【PHP8.x】flock()関数の使い方
flock関数の使い方について、初心者にもわかりやすく解説します。
基本的な使い方
flock関数は、開いているファイルに対してロックの取得や解放を実行する関数です。Webアプリケーションなど、複数のプロセスが同時に同じファイルへアクセスする環境では、ファイルへの書き込み処理が衝突し、データが破損してしまう可能性があります。flock関数は、このような競合状態を防ぎ、ファイルの整合性を保つために使用される排他制御の仕組みです。第一引数にはfopen関数などで取得したファイルポインタを、第二引数にはロックの種類を指定する定数を渡します。例えば、他のプロセスからの読み込みは許可するが書き込みは防ぐ「共有ロック(LOCK_SH)」や、自分以外の全てのプロセスからの読み書きをブロックする「排他ロック(LOCK_EX)」などがあります。ファイルへの処理が完了した後は、必ず「ロック解放(LOCK_UN)」を指定してロックを解除する必要があります。このロックはアドバイザリロックと呼ばれる協調的な仕組みであり、flock関数を使用するプロセス間でのみ有効です。ロックの取得や解放に成功した場合はtrueを、失敗した場合はfalseを返します。
構文(syntax)
1flock(resource $file, int $operation): bool
引数(parameters)
resource $stream, int $operation, ?int &$would_block = null
- resource $stream: ロックをかけるファイルリソースを指定します。
fopen()などで取得したファイルポインタを指定します。 - int $operation: 実行するロック操作を指定します。
LOCK_SH(共有ロック)、LOCK_EX(排他ロック)、LOCK_UN(ロック解除)などの定数を使用します。 - ?int &$would_block = null: ロックがすぐに取得できなかった場合に、その旨を示す整数値が格納されます。
trueの場合はロックがブロックされたことを示します。
戻り値(return)
bool
ファイルロック操作の成功・失敗を示す真偽値(boolean)が返されます。ロック操作が成功した場合は true、失敗した場合は false が返されます。
サンプルコード
PHP flockによるタイムアウト付きファイルロック取得
1<?php 2 3/** 4 * ファイルに対してタイムアウト付きで排他ロックを取得し、処理を実行します。 5 * 6 * @param string $filePath ロックするファイルのパス 7 * @param int $timeoutSeconds ロック取得を試みる最大時間(秒) 8 * @param int $pollIntervalMicroseconds ロック状態をポーリングする間隔(マイクロ秒) 9 * @return bool ロック取得と処理の実行が成功した場合は true、それ以外は false 10 */ 11function acquireFileLockWithTimeout( 12 string $filePath, 13 int $timeoutSeconds = 10, 14 int $pollIntervalMicroseconds = 500000 // 0.5秒 15): bool { 16 $fp = null; 17 $locked = false; // ロックが取得できたかどうかのフラグ 18 19 try { 20 // ファイルを開く (読み書きモード、ファイルが存在しない場合は作成) 21 // 'c+' はファイルが存在しない場合は作成し、読み書きモードで開きます。 22 // ファイルポインタはファイルの先頭に置かれます。 23 $fp = fopen($filePath, 'c+'); 24 if ($fp === false) { 25 echo "エラー: ファイルを開けませんでした: {$filePath}\n"; 26 return false; 27 } 28 29 $startTime = time(); 30 $wouldBlock = null; // flockがブロックされた場合に設定される変数 (PHP 8で導入) 31 32 // 指定されたタイムアウト時間内でロック取得を試行 33 while ((time() - $startTime) < $timeoutSeconds) { 34 // flock($fp, LOCK_EX | LOCK_NB, $wouldBlock) 35 // LOCK_EX: 排他ロック(他のプロセスはファイルの読み書きができません) 36 // LOCK_NB: 非ブロッキングモード(ロックが取得できない場合でも、すぐにfalseを返します) 37 // $wouldBlock: LOCK_NB指定時にロック取得に失敗した場合、true/1が設定されます。 38 if (flock($fp, LOCK_EX | LOCK_NB, $wouldBlock)) { 39 $locked = true; 40 echo "ファイルロックを取得しました。\n"; 41 break; // ロック成功、ループを抜ける 42 } elseif ($wouldBlock) { 43 // ロックが他のプロセスによってブロックされている場合 44 // 少し待機してから再試行します。 45 usleep($pollIntervalMicroseconds); 46 } else { 47 // flockが予期せず失敗した場合($wouldBlockが設定されない、またはfalseの場合) 48 echo "エラー: flock関数が予期せず失敗しました。\n"; 49 return false; 50 } 51 } 52 53 if (!$locked) { 54 echo "エラー: ファイルロックの取得に失敗しました (タイムアウト: {$timeoutSeconds}秒)。\n"; 55 return false; 56 } 57 58 // --- ここにロック中に実行したい処理を記述します --- 59 // 例として、ファイルにタイムスタンプを追記します。 60 echo "ロック中に処理を実行します... (2秒間待機)\n"; 61 sleep(2); // 処理を模倣するために2秒間一時停止 62 63 // ファイルの末尾に内容を追記 64 file_put_contents($filePath, "Locked at " . date('Y-m-d H:i:s') . "\n", FILE_APPEND); 65 echo "処理が完了しました。\n"; 66 // -------------------------------------------------- 67 68 return true; // 処理成功 69 70 } catch (Throwable $e) { // PHP 7からExceptionだけでなくErrorもキャッチできるようThrowableを使用 71 echo "致命的なエラーが発生しました: " . $e->getMessage() . "\n"; 72 return false; 73 } finally { 74 // ファイルハンドルが開いている場合、ロックを解除しファイルを閉じます 75 if ($fp !== null) { 76 if ($locked) { 77 flock($fp, LOCK_UN); // LOCK_UN: ロック解除 78 echo "ファイルロックを解除しました。\n"; 79 } 80 fclose($fp); 81 } 82 } 83} 84 85// --- サンプルコードの実行例 --- 86// このコードを複数同時に実行して、ロックが機能するかテストできます。 87$fileToLock = 'my_application.lock'; 88$timeout = 5; // ロック取得のタイムアウトを5秒に設定 89 90echo "ファイルロック取得処理を開始します...\n"; 91if (acquireFileLockWithTimeout($fileToLock, $timeout)) { 92 echo "ファイルロック処理が正常に完了しました。\n"; 93} else { 94 echo "ファイルロック処理が失敗しました。\n"; 95} 96 97?>
PHPのflock関数は、複数のプログラムが同時にファイルへアクセスする際に、データの破損を防ぎ、整合性を保つための「ファイルロック」機能を提供します。この関数は、ファイルリソース、実行するロック操作の種類(排他ロック、共有ロック、ロック解除など)、そしてPHP 8で導入された、ロックがブロックされた場合にその状態を示す変数$would_blockを引数に指定します。ロック操作が成功したか否かは、戻り値の真偽値で判断できます。
サンプルコードは、flock関数を用いて、指定されたタイムアウト時間内にファイルロックを取得し、その間に処理を実行する仕組みを示しています。LOCK_EX(排他ロック)とLOCK_NB(非ブロッキングモード)を組み合わせて使用し、ロックがすぐに取得できない場合でも処理を停止させずに$would_block引数からロックの競合状態を判断します。ロックがブロックされている間は短い時間待機(usleep)し、タイムアウトになるまでロック取得を繰り返し試みます。これにより、ファイルへの排他的アクセスを一定時間内で安全に確保し、処理完了後にロックを解除する堅牢なファイル操作が実現されます。これは、複数のプログラムが共有リソースを安全に扱うための重要な技術です。
flock関数は、指定されたファイル全体をロックします。NFSなどのネットワークファイルシステム上では、期待通りに動作しない場合があるため注意が必要です。
サンプルコードでは、LOCK_NB(非ブロッキングモード)を指定することで、ロックが取得できない場合でも処理をブロックせず、すぐに結果を返します。PHP 8以降では、$would_block引数を通じて、ロックが他のプロセスによってブロックされたかどうかを確認できるため、タイムアウト処理を実装する際に役立ちます。
ロック取得に失敗した場合にusleepで短い間隔を置いて再試行することは、CPU負荷を抑えつつ、ロックが解放されるのを待つために重要です。
finallyブロックは、ロックの成否にかかわらず、確実にファイルロックを解除し、ファイルハンドルを閉じるために必須です。これにより、リソースの解放漏れやデッドロックといった問題を未然に防ぎ、アプリケーションの安定性を高めます。
PHP flock による安全なファイルロックとデッドロック回避
1<?php 2 3/** 4 * 安全なファイルロック処理を実行する関数。 5 * デッドロックのリスクを考慮し、非ブロックモードとタイムアウトでロックを試みる。 6 * 7 * デッドロックは、複数のプロセスが互いにリソースの解放を待機し、 8 * 結果としてどのプロセスも進行できなくなる状態を指します。 9 * 例えば、プロセスAがファイル1をロックし、ファイル2をロックしようとし、 10 * その一方でプロセスBがファイル2をロックし、ファイル1をロックしようとすると発生します。 11 * 12 * この関数では、flock関数のLOCK_NB (非ブロックモード) を使用し、 13 * ロック取得に失敗した場合は直ちに処理を停止せず、指定回数リトライを試みることで、 14 * 一時的な競合状態を管理し、無限待機(デッドロックの原因の一つ)を防ぎます。 15 * より複雑なデッドロック回避策には、すべてのプロセスで同じ順序でロックを取得する規約を設ける、 16 * ロックマネージャを導入する、またはデッドロック検出と回復のメカニズムを実装するなどがあります。 17 * 18 * @param string $filePath ロック対象のファイルのパス。 19 * @param int $timeout ロックを試みる最大秒数。 20 * @param int $retryInterval ロックを再試行する間隔(マイクロ秒)。 21 * @return resource|false 成功した場合はファイルポインタ、失敗した場合はfalse。 22 */ 23function acquireSafeFileLock(string $filePath, int $timeout = 5, int $retryInterval = 100000) 24{ 25 // ファイルを開く。ファイルが存在しない場合は作成される。 26 // 'c+' モード: 読み書き用にファイルを開き、存在しない場合は作成する。 27 $fileHandle = @fopen($filePath, 'c+'); 28 29 if ($fileHandle === false) { 30 error_log("Failed to open file: {$filePath}"); 31 return false; 32 } 33 34 $startTime = microtime(true); 35 $lockAcquired = false; 36 37 while (microtime(true) - $startTime < $timeout) { 38 // 排他ロックを非ブロックモードで取得しようと試みる 39 // LOCK_EX: 排他ロック (書き込みロック) を取得する 40 // LOCK_NB: 非ブロックモード。ロック取得に失敗した場合、すぐにfalseを返し、待機しない 41 if (flock($fileHandle, LOCK_EX | LOCK_NB)) { 42 $lockAcquired = true; 43 break; // ロック取得成功 44 } 45 46 // ロック取得に失敗した場合、少し待機して再試行 47 usleep($retryInterval); // マイクロ秒単位で待機 48 } 49 50 if (!$lockAcquired) { 51 // タイムアウトした場合はファイルハンドルを閉じ、エラーログを出力 52 fclose($fileHandle); 53 error_log("Failed to acquire lock for file: {$filePath} within {$timeout} seconds."); 54 return false; 55 } 56 57 // ロックが取得できた場合はファイルハンドルを返す 58 // 呼び出し側で fclose() および flock($fileHandle, LOCK_UN) を行う必要がある 59 return $fileHandle; 60} 61 62// --- サンプルコードの実行 --- 63 64// 一時ディレクトリにロックファイルを作成 65$lockFilePath1 = sys_get_temp_dir() . '/my_app_lock_file_1.lock'; 66$lockFilePath2 = sys_get_temp_dir() . '/my_app_lock_file_2.lock'; 67 68// このサンプルコードは単一のPHPスクリプト内でデッドロックを直接発生させるものではありません。 69// 複数のプロセスが同時に実行された場合にデッドロックに陥る可能性のある状況を示唆し、 70// その回避策として acquireSafeFileLock 関数の利用を推奨します。 71 72// シナリオ例: 73// プロセスA: acquireSafeFileLock(file1) -> acquireSafeFileLock(file2) 74// プロセスB: acquireSafeFileLock(file2) -> acquireSafeFileLock(file1) 75// 76// このように異なる順序で複数のリソースのロックを試みると、互いのロック解放を待って 77// デッドロックが発生しえます。これを回避するためには、すべてのプロセスで同じ順序で 78// ロックを取得する規約を設ける、またはロック取得にタイムアウトを設定するなどの工夫が必要です。 79 80echo "--- ロック処理の開始 ---\n"; 81 82// ファイル1のロックを試みる 83echo "Attempting to acquire lock for file 1 ({$lockFilePath1})...\n"; 84$file1Handle = acquireSafeFileLock($lockFilePath1, 5, 50000); // 5秒タイムアウト、50msリトライ 85 86if ($file1Handle) { 87 echo "Lock for file 1 acquired successfully. Performing work...\n"; 88 // 実際の処理... 例えば、ファイルへの書き込みなど 89 fwrite($file1Handle, "Data written with lock on file 1.\n"); 90 sleep(1); // 処理をシミュレート 91 92 // ここでファイル2のロックも必要になる、というシナリオを想定 93 // ただし、この単一スクリプトではファイル2のロックが他のプロセスと競合することはない 94 echo "Attempting to acquire lock for file 2 ({$lockFilePath2})...\n"; 95 $file2Handle = acquireSafeFileLock($lockFilePath2, 5, 50000); 96 97 if ($file2Handle) { 98 echo "Lock for file 2 acquired successfully. Performing more work...\n"; 99 fwrite($file2Handle, "Data written with lock on file 2.\n"); 100 sleep(1); // 処理をシミュレート 101 102 // ファイル2のロックを解除し、ファイルを閉じる 103 flock($file2Handle, LOCK_UN); 104 fclose($file2Handle); 105 echo "Lock for file 2 released.\n"; 106 } else { 107 echo "Failed to acquire lock for file 2. This could indicate high contention or a potential deadlock scenario if multiple processes were involved.\n"; 108 } 109 110 // ファイル1のロックを解除し、ファイルを閉じる 111 flock($file1Handle, LOCK_UN); 112 fclose($file1Handle); 113 echo "Lock for file 1 released.\n"; 114} else { 115 echo "Failed to acquire lock for file 1. Another process might hold the lock, or a timeout occurred.\n"; 116} 117 118echo "--- ロック処理の終了 ---\n"; 119 120// ロックファイルの後処理 (オプション: スクリプト終了後にロックファイルを削除) 121if (file_exists($lockFilePath1)) { 122 unlink($lockFilePath1); 123 echo "Cleaned up lock file: {$lockFilePath1}\n"; 124} 125if (file_exists($lockFilePath2)) { 126 unlink($lockFilePath2); 127 echo "Cleaned up lock file: {$lockFilePath2}\n"; 128} 129 130?>
PHPのflock関数は、複数のプログラムやプロセスが同時に同じファイルへアクセスする際に、データの一貫性を保つためのファイルロック機能を提供します。この関数は、対象のファイルポインタ($stream)と、共有ロックや排他ロックといった操作の種類($operation)を指定して呼び出し、ロックの取得に成功したかを真偽値(bool)で返します。
提示されたサンプルコードは、このflock関数をより安全に利用するためのacquireSafeFileLock関数を定義しています。特に、複数のプロセスが互いにリソースの解放を待ってしまい、処理が停止する「デッドロック」の回避策に焦点を当てています。具体的には、flock関数のLOCK_NBオプション(非ブロックモード)を活用し、ロックがすぐに取得できなかった場合でも、処理を待機せずに指定された回数や時間($timeout)内でロックの取得を再試行します。これにより、ロックが取得できない場合に無限に待ち続けることを防ぎ、デッドロックの発生リスクを軽減します。
acquireSafeFileLock関数は、ロックに成功した場合は開かれたファイルポインタを返し、失敗した場合はfalseを返します。ロックが成功した場合、呼び出し側は処理の完了後に必ずflock($fileHandle, LOCK_UN)でロックを解除し、fclose($fileHandle)でファイルを閉じる必要があります。このアプローチは、システムエンジニアがWebアプリケーションなどで共有リソースを扱う際に、安定性と信頼性を高める上で非常に重要です。
flockでファイルロックを取得した際は、必ずflock($fileHandle, LOCK_UN)で解除し、fclose()でファイルハンドルを閉じてください。解除忘れは他のプロセスの無限待機を招きます。複数のファイルをロックする場合は、常に同じ順序で取得する規約を設けることがデッドロック回避に最も効果的です。LOCK_NBオプションとリトライ、タイムアウトの組み合わせは、無限待機を防ぎ、一時的な競合状態に対応する有効な手段です。必ずエラーログを適切に出力し、失敗時の処理を明確にしましょう。flockはネットワークファイルシステムでの信頼性が低いため、ローカルファイルシステムでの利用を推奨します。
PHP flock lock_nb でノンブロッキングロックを取得する
1<?php 2 3/** 4 * ノンブロッキングファイルロックを使用して、排他処理を実行する関数。 5 * 他のプロセスがロックしている場合は処理をスキップします。 6 * 7 * @param string $lockFilePath ロック対象となるファイルへのパス。 8 */ 9function processWithNonBlockingLock(string $lockFilePath): void 10{ 11 // ロック対象のファイルを読み書きモード ('c+') でオープンします。 12 // 'c+' モードは、ファイルが存在しない場合は作成し、存在する場合は既存の内容を切り詰めずに開きます。 13 $fileHandle = fopen($lockFilePath, 'c+'); 14 15 if ($fileHandle === false) { 16 echo "エラー: ロックファイルのオープンに失敗しました。\n"; 17 return; 18 } 19 20 echo "プロセスID: " . getmypid() . " - ロックの取得を試行中...\n"; 21 22 // flock関数で排他ロック (LOCK_EX) をノンブロッキング (LOCK_NB) で試みます。 23 // ロックが取得できた場合は true、できなかった場合は false を返します。 24 if (flock($fileHandle, LOCK_EX | LOCK_NB)) { 25 // ロックの取得に成功した場合の処理 26 echo "プロセスID: " . getmypid() . " - ロックを取得しました。排他処理を実行します。\n"; 27 28 // 排他処理の例: ファイルへの書き込み 29 fwrite($fileHandle, "データ書き込み (PID: " . getmypid() . "): " . date('Y-m-d H:i:s') . "\n"); 30 fflush($fileHandle); // バッファをフラッシュしてすぐにディスクに書き込む 31 32 // 他のプロセスがロックを取得できないように、しばらく待機します。 33 sleep(3); 34 35 echo "プロセスID: " . getmypid() . " - 排他処理が完了しました。\n"; 36 37 // ロックを解除します。 38 flock($fileHandle, LOCK_UN); 39 echo "プロセスID: " . getmypid() . " - ロックを解除しました。\n"; 40 } else { 41 // ロックの取得に失敗した場合の処理(ノンブロッキングなので、すぐに制御が戻ります) 42 echo "プロセスID: " . getmypid() . " - ロックの取得に失敗しました。他のプロセスがファイルを使用中です。処理をスキップします。\n"; 43 } 44 45 // ファイルハンドルを閉じます。 46 fclose($fileHandle); 47 echo "プロセスID: " . getmypid() . " - スクリプトが終了しました。\n"; 48} 49 50// スクリプトの実行例 51// 現在のディレクトリに 'app.lock' というファイルをロック対象として使用します。 52$lockFile = __DIR__ . '/app.lock'; 53processWithNonBlockingLock($lockFile);
PHPのflock関数は、複数のプロセスが同時に一つのファイルを操作する際に、データの競合を防ぐためのファイルロック機能を提供します。この関数は、fopenで開いたファイルハンドル($stream)に対して、どのような種類のロックを行うか($operation)を指定して使用します。
サンプルコードでは、LOCK_EX | LOCK_NBという操作モードが指定されています。LOCK_EXは排他ロックを意味し、他のプロセスがそのファイルをロックできないようにします。LOCK_NB(ノンブロッキング)は、ロックがすぐに取得できない場合でも処理がブロックされず、即座に失敗(false)を返すように指示します。これにより、他のプロセスがロックしている場合は待機せずに、すぐに次の処理へ移ることができます。
flock関数の戻り値はbool型で、ロックの取得に成功すればtrue、失敗すればfalseが返されます。サンプルコードでは、ロック成功時のみ排他処理(ファイルへの書き込み)を実行し、処理完了後にLOCK_UNでロックを解除しています。このノンブロッキングファイルロックは、例えばバッチ処理の二重起動防止など、特定の処理が複数同時に実行されるのを避けたい場合に非常に役立ちます。
flock関数はファイルそのものではなく、開かれたファイルハンドルに対してロックをかけます。複数のファイルハンドルが存在する場合、それぞれ独立したロック状態を持つ点に注意が必要です。LOCK_NBフラグは、他のプロセスがロック中の場合でも処理をブロックせず、すぐにロック取得の成否を返しますので、サーバーアプリケーションなどで応答性を保つために活用ください。ロックはファイルハンドルを閉じるか、LOCK_UNで明示的に解除するまで有効です。プログラム終了時には自動的に解除されますが、処理完了後の明示的な解除はコードの意図を明確にします。fopenのc+モードはロックファイルに適していますが、パーミッション不足などでファイルオープンに失敗する可能性もあるため、エラーハンドリングを丁寧に行いましょう。fwrite後のfflushは、書き込んだデータをOSのバッファから物理ディスクに確実に書き込むことで、データの整合性を高める助けになります。
PHP flock でファイルロックを安全に取得する
1<?php 2 3/** 4 * ファイルロックを使用して安全にファイルに書き込む処理を管理します。 5 * flockはファイル記述子に対して動作するため、プロセス間ロックに使用されます。 6 * 7 * @param string $filePath ロックするファイルのパス 8 * @param string $content ファイルに書き込む内容 9 */ 10function manageFileLock(string $filePath, string $content): void 11{ 12 // ファイルを開く。'c+'モードは、ファイルが存在しない場合は作成し、読み書きを許可します。 13 // ファイルポインタはファイルの先頭に置かれます。 14 $fileHandle = fopen($filePath, 'c+'); 15 16 if ($fileHandle === false) { 17 echo "エラー: ファイル '{$filePath}' を開けませんでした。\n"; 18 return; 19 } 20 21 echo "ブロックする排他ロックを取得しようとしています...\n"; 22 23 // 排他ロック (LOCK_EX) をブロックモードで取得します。 24 // 他のプロセスがロックを保持している場合、ここでスクリプトは待機します。 25 if (flock($fileHandle, LOCK_EX)) { 26 echo "ブロックする排他ロックを正常に取得しました。\n"; 27 28 // ロックが保持されている間に他のプロセスがアクセスしようとする状況をシミュレートするため、 29 // 2秒間待機します。 30 echo "ロックを2秒間保持中 (処理シミュレーション)... \n"; 31 sleep(2); 32 33 // ファイルの内容をクリアし、ポインタを先頭に戻してから新しい内容を書き込みます。 34 ftruncate($fileHandle, 0); // ファイルを0バイトに切り詰める 35 rewind($fileHandle); // ファイルポインタを先頭に戻す 36 fwrite($fileHandle, $content); 37 fflush($fileHandle); // バッファされた出力を強制的にファイルに書き込む 38 39 echo "ファイルに書き込みました。\n"; 40 41 // 同じファイルに対して、別のファイルハンドルを使って非ブロックロックを試みます。 42 // これは、最初のロックがまだ保持されているため、失敗すると予想されます。 43 echo "\n同じファイルに対し、別のハンドルで非ブロックロックを試みます...\n"; 44 $secondFileHandle = fopen($filePath, 'c+'); 45 if ($secondFileHandle === false) { 46 echo "エラー: 2番目のファイルハンドルを開けませんでした。\n"; 47 } else { 48 $wouldBlock = null; 49 // 排他ロック (LOCK_EX) を非ブロックモード (LOCK_NB) で取得しようとします。 50 if (flock($secondFileHandle, LOCK_EX | LOCK_NB, $wouldBlock)) { 51 // ここに到達した場合、予想外にロックが取得されたことを示します。 52 // (例: 最初のロックがすでに解放されていた、またはファイルシステム固有の動作) 53 echo "警告: 2番目のハンドルで非ブロックロックを予期せず取得しました。\n"; 54 flock($secondFileHandle, LOCK_UN); // すぐに解放します 55 } else { 56 // ロック取得に失敗した場合の処理 57 if ($wouldBlock) { 58 // $wouldBlockがtrueの場合、他のプロセスがロックを保持しているため、 59 // 非ブロック操作が失敗したことを意味します。これが「flockが効かない」 60 // と感じられる主なケースの1つですが、実際には「意図通りブロックされた」 61 // と解釈すべきです。 62 echo "2番目のハンドルでの非ブロックロックは、ブロックされることが検出されました (期待通り)。\n"; 63 echo "これがLOCK_NBで「flockが効かない」場合に対処する方法です: ロック済みと報告されます。\n"; 64 } else { 65 // $wouldBlockがfalseの場合、一般的なflockのエラーです (例: ファイルシステムがロックをサポートしていない)。 66 echo "エラー: 2番目のハンドルでの非ブロックロックは、不明な理由で失敗しました。\n"; 67 } 68 } 69 fclose($secondFileHandle); // 2番目のファイルハンドルを閉じます 70 } 71 72 // 最初のロックを解放します。 73 flock($fileHandle, LOCK_UN); 74 echo "\n最初のロックを解放しました。\n"; 75 } else { 76 // ブロックするロックの取得に失敗した場合。これは通常、ファイルシステムの問題や権限の問題です。 77 // 他のプロセスがロックを保持している場合、flockは待機するため、ここには到達しません。 78 echo "エラー: ファイル '{$filePath}' のブロックする排他ロックを取得できませんでした。\n"; 79 } 80 81 // 最初のファイルハンドルを閉じます。 82 fclose($fileHandle); 83} 84 85// ロックファイルとして使用するパスを定義 86$lockFilePath = __DIR__ . '/app_data.lock'; 87$dataContent = "アプリケーションデータ最終更新: " . date('Y-m-d H:i:s') . "\n"; 88 89// ロック管理ロジックを実行 90manageFileLock($lockFilePath, $dataContent); 91 92// オプション: サンプル実行後にロックファイルを削除する場合 (開発/テスト用途) 93// unlink($lockFilePath);
PHPのflock関数は、複数のPHPプロセスが同じファイルへ同時に書き込みを行う際に、データの破損を防ぎ、整合性を保つための「ファイルロック」機能を提供します。これは、ファイルを開いた際に得られるリソース $stream に対して動作し、どのようにロックするかを $operation 引数で指定します。$operationには、他のプロセスからの書き込みを禁止する「排他ロック (LOCK_EX)」や、複数プロセスでの読み込みを許可する「共有ロック (LOCK_SH)」、そしてロックを解放する「ロック解除 (LOCK_UN)」などがあります。
サンプルコードでは、まずファイルをc+モードで開いた後、flock($fileHandle, LOCK_EX)で排他ロックをブロックモードで取得しています。これにより、他のプロセスがロックを保持している場合は、このスクリプトはロックが解放されるまで待機します。ロック取得後は、ファイルに安全に書き込みを行い、処理が完了したらflock($fileHandle, LOCK_UN)でロックを解放します。
特に注目すべきは、flock($secondFileHandle, LOCK_EX | LOCK_NB, $would_block)の部分です。ここでは、非ブロックモード (LOCK_NB) で排他ロックの取得を試みています。非ブロックモードは、ロック取得に失敗しても待機せず、すぐに結果を返します。この時、もしロック取得に失敗した原因が「他のプロセスがロックを保持しているため」であれば、オプション引数 $would_block にtrueがセットされます。初心者が「flockが効かない」と感じるケースの多くは、このLOCK_NBが既存のロックによってブロックされ、意図通りロックが取得できなかった状況を指します。$would_blockを確認することで、その原因を正確に把握し、対処することができます。flockはロックの取得・解放が成功した場合はtrue、失敗した場合はfalseを戻り値として返します。
flockはファイル記述子に対して動作し、主に複数のプロセス間での排他制御に利用されます。LOCK_NBを指定した際にロックが取得できない場合、返り値がfalseとなり、第三引数の$would_blockで他のプロセスがロックを保持しているか確認できます。これは「flockが効かない」のではなく、意図通りブロックされている状態ですので注意が必要です。NFSなどのネットワークファイルシステムでは、flockが期待通り機能しない場合があるため、動作保証が難しいことがあります。ロックを取得した後は、必ずflock(LOCK_UN)で解放するか、ファイルハンドルを閉じるようにしましょう。解放し忘れは他のプロセスのデッドロックの原因となります。