※このサンプルの解説は、「CGI(Perl)の解説」をすべて読み終えてる方を前提としています。
後になるほど省略されている場合がありますのでできるだけ順番に読んでください。
また、実行に際しては、サーバーでPHPが利用できる事を前提に解説しています。
■ ファイル書き込みの排他制御
ファイル書き込み時の排他制御を行う場合、簡単な方法は「flock()」関数を使う方法です。
- flock((ファイルポインタ), (処理区分))
-
ファイルをロック、ロック解除する。
処理が成功すれば「TRUE」を失敗すれば「FALSE」を返す。
処理区分 意 味 LOCK_SH 共有ロック指定 LOCK_EX 排他ロック指定 LOCK_UN ロックの解除
※「flock()」関数は私のサーバーで正常に動作しませんでした。
したがって、後半の「mkdir」を使用した排他制御の方をお使い下さい。
Windows以外でも動作します。
「flock()」関数で共有ロックを行いたい場合は次のように記述します。
- 【記述サンプル】
- $filnam = "acclogf.cgi"; $fp = fopen($filnam,"w"); flock($fp, LOCK_SH); fputs($fp,"aaa\n"); flock($fp, LOCK_UN); fclose($fp);
この場合、別の処理からこのファイルに対して書き込みはできなくなりますが、読み込みは可能です。
同様に排他ロックを行いたい場合は次のように記述します。
- 【記述サンプル】
- $filnam = "acclogf.cgi"; $fp = fopen($filnam,"w"); flock($fp, LOCK_EX); fputs($fp,"aaa\n"); flock($fp, LOCK_UN); fclose($fp);
例えば、アクセスカウンタでカウンタ値を読んでから書くまでの間に、別の処理が同様の処理を行うとカウンタが正しく記録できなかったり、カウンタ用のファイルが壊れてしまう場合があります。
そういった場合の処理方法として、仮のファイルを作成しておき、処理前にそのファイルに排他ロックを掛け、処理が終了すれば解除するようにします。
- 【 解 説 】
-
カウントアップの処理を行う前に、そのファイルに排他ロックが掛けれるかどうかをチェックし、できなければ「何秒か待つ」という処理を何回か行うようにします。
このようにすれば、カウンタ用のファイルは必ずひとつの処理しか受け付けなくなり、ファイルが壊れることはほとんどなくなります。
まず、排他制御用の仮のファイルを「lock.cgi」という名前で作成しておきます。
パーミッションは「666」です。次にこのファイルを排他ロックする場合は次のように記述できます。
$filnam = "lock.cgi"; $fp = fopen($filnam,"r"); flock($fp, LOCK_EX);排他ロックを行う場合、「FALSE」が返ってくれば1秒待って繰り返す、という処理に変更すると次のようなります。
while(flock($fp, LOCK_EX) == FALSE){ sleep(1); }この場合、ひとつ問題があります。
万一、「lock.cgi」というファイルが排他ロックが解除されてなかった場合、無限ループに陥ります。そこで、上記の処理を5回(5秒)試しても排他ロックがかかっている場合はメッセージを表示して中止するという処理を付け加えます。
$scnt0 = 0; while(flock($fp, LOCK_EX) == FALSE){ if ($scnt0++ >= 5){ print "ロックできません。"; exit(); } sleep(1); }
上記の命令を確認するため次のようなサンプルを作ってみました。
下記サンプルの「error_reporting(0);」は「flock()」関数でエラーが発生した場合、PHPから警告メッセージが表示されるのを止めるためです。
- 【記述サンプル】
- <?php print <<< END_DOC <HTML> <HEAD> <title>ファイル書き込みの排他制御</title> </HEAD> <BODY> END_DOC; error_reporting(0); $filnam = "lock.cgi"; $fp = fopen($filnam,"w");fclose($fp); $fp = fopen($filnam,"r"); $scnt0 = 0; while(flock($fp, LOCK_EX) === FALSE){ if ($scnt0++ >= 3){ print "1回目ロックできません。<br>\n"; fclose($fp); exit(); } print "1回目休み$scnt0<br>\n"; sleep(1); } print "1回目ロックできました。<br>\n"; $fp1 = fopen($filnam,"r"); $scnt0 = 0; while(flock($fp1, LOCK_EX) === FALSE){ if ($scnt0++ >= 3){ print "2回目ロックできません。<br>\n"; fclose($fp1); exit(); } print "2回目休み$scnt0<br>\n"; sleep(1); } print "2回目ロックできました。<br>\n"; fclose($fp); print <<< END_DOC </BODY> </HTML> END_DOC; ?>
◆実行結果について
上記のサンプルを「flock.php」という名前で保存して実行してみてください。
※ただし、この処理は私のサーバーで正常に動作しませんでしたので、実行サンプルはありません。
また、Windows環境でも動作しません。
したがって、後半の「mkdir」を使用した排他制御の方をお使い下さい。
Windows以外でも動作します。
◆「mkdir」を使用した排他制御
Windows環境の場合、この「flock()」関数が使用できないという問題があります。
そこで、Windowsでも実行できるように、ディレクトリ(フォルダ)作成の命令を使った方法も解説しておきます。
- 【 解 説 】
-
まず、ディレクトリ(フォルダ)を作成する命令の「mkdir()」関数は、そのディレクトリ(フォルダ)が存在しなければそのディレクトリ(フォルダ)を作成して正常に終了します。
そのディレクトリ(フォルダ)が存在すれば、「FALSE」を返します。例えば、「lock」という名前のディレクトリ(フォルダ)を作成する場合は次のように記述します。
mkdir("lock");そして、上記で解説した処理を「mkdir()」に変更すると次のようになります。
$dirnam = "lock"; $scnt0 = 0; while(mkdir($dirnam) == FALSE){ if ($scnt0++ >= 5){ print "ロックできません。"; exit(); } sleep(1); }また、処理が終わったら先ほど作成したディレクトリ(フォルダ)を削除しておかないと2度と書き込みができなくなります。
ディレクトリ(フォルダ)の削除は「rmdir()」関数を使い、次のように記述できます。
rmdir("lock");
では、上記の命令を確認するため次のようなサンプルを作ってみました。
下記サンプルの「error_reporting(0);」は「mkdir()」関数でエラーが発生した場合、PHPから警告メッセージが表示されるのを止めるためです。
- 【記述サンプル】
- <?php print <<< END_DOC <HTML> <HEAD> <title>ファイル書き込みの排他制御</title> </HEAD> <BODY> END_DOC; error_reporting(0); $dirnam = "lock"; $scnt0 = 0; while(mkdir($dirnam) === FALSE){ if ($scnt0++ >= 3){ print "1回目ロックできません。<br>\n"; rmdir($dirnam); exit(); } print "1回目休み$scnt0<br>\n"; sleep(1); } print "1回目ロックできました。<br>\n"; print "--------------------<br>\n"; $scnt0 = 0; while(mkdir($dirnam) === FALSE){ if ($scnt0++ >= 3){ print "2回目ロックできません。<br>\n"; rmdir($dirnam); exit(); } print "2回目休み$scnt0<br>\n"; sleep(1); } print "2回目ロックできました。<br>\n"; rmdir($dirnam); print <<< END_DOC </BODY> </HTML> END_DOC; ?>
◆実行結果について
上記のサンプルを「flockw.php」という名前で保存して実行してみてください。
実行結果はこちらをクリックしてください。
なお、実行結果については、各々のサーバーの環境、ユーザーの環境にによって内容は変わってきます。