flock のモード切替え時の挙動は環境によって異なる

flock() でロック獲得中にロック状態を切り替える(LOCK_SH から LOCK_EX にする、など)と、環境や状況によってロックが保持されない場合があります。
 
検証用コード:
A… ロック切り替えプロセス(keeper.php )

<?php
$f=fopen(“t.txt”,”r+”);
for($i = 0; $i < 10; $i++){
  // ロック切り替えまたは取得(LOCK_EX -> LOCK_SH)
  echo “shared($i)\n”;
  flock($f, LOCK_SH);
  sleep(3);
  // ロック切り替え(LOCK_SH -> LOCK_EX)
  echo “exclusive($i)\n”;
  flock($f, LOCK_EX);sleep(3);
}
echo “Done! the treasure was protected!\n”; // ここまで theif.php にロックがとられなければ期待通り。
?>

B…ロック奪取プロセス(thief.php)

<?php
$f=fopen(“t.txt”, “r+”);
flock($f, LOCK_EX); // ロック待ち
// ロック奪取成功
echo “lock obtained!\n”;
echo “im doing something, hehehe.”;
for($i=0;$i<10;$i++){
  sleep(1);
  echo “.”;
}
echo “\nfinished! bye ;P\n”;’
fclose($f); // ロック開放
?>

実行:

$ touch t.txt
$ nice php locker.php &
$ php thief.php

結果:
Windows XP SP2 + Cygwin + PHP 5.1.2 (FS: NTFS) … ロック保持されない(thief won)
Linux 2.4.21-50.ELsmp + PHP 4.4.6 (FS: ext3) … ロック保持される(keeper won)
FreeBSD 6.2 + PHP 5.2.5 (FS: ufs) … ロック保持される(keeper won)
 
Windows XP (FS: NTFS) はロック自体はできますが、LOCK_EX <-> LOCK_SH の切り替えでいったんロックが解除されるという挙動になりました。
 
なお、PHP マニュアルの flock() の項にもあるとおりファイルシステムが FAT の場合など同じ Windows でも環境によっては結果が変わるため、この結果を鵜呑みにせず必ずご自身の環境でテストしてください。


追記(2008-01-09):
ちなみに、PHP 5.2.5 でソースをたどってみたところ PHP の flock() は ext/standard/flock_compat.c で flock (2) ではなく fcntl (2) を使って実装されていました。Linux や BSD など fcntl でロック変換がアトミックにできる場合は問題なく動作しそうです。
なお Windows で、かつ fcntl がない(Cygwin など UNIX 擬似環境ビルドではない)場合は、 UNIX と互換性があるように LockFileEx() で実装されています。