Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【PHP8.x】SplFileObject::flock()メソッドの使い方

flockメソッドの使い方について、初心者にもわかりやすく解説します。

作成日: 更新日:

基本的な使い方

flockメソッドは、SplFileObjectクラスが表すファイルに対して、ロックをかける処理を実行するメソッドです。これは、複数のプログラムやプロセスが同時に一つのファイルにアクセスし、データの書き込みや読み込みを行う際に、データの破壊や不整合を防ぐために非常に重要です。例えば、複数のユーザーが同時に同じ設定ファイルを更新しようとした場合、ファイルロックがないと一部の変更が失われたり、ファイルの内容が壊れてしまう可能性があります。

このメソッドでは、引数を通じてロックの種類を指定します。具体的には、他のプログラムからの読み込みは許可しつつ書き込みは禁止する「共有ロック」、他のどんなアクセスも一切許さない「排他ロック」、そして適用済みのロックを解除する操作などがあります。また、ロックがすぐに取得できない場合に、処理がロックの解放を待つかどうかを指定することも可能です。

flockメソッドは、指定されたロック操作が成功した場合はtrueを返し、失敗した場合はfalseを返します。これにより、ロックの取得状況に基づいて、プログラムの次の処理を適切に制御することができます。ファイル操作において安全性を確保するために、このメソッドはデータの整合性を保つ上で不可欠な機能を提供します。

構文(syntax)

1<?php
2$file = new SplFileObject('path/to/your/file.txt', 'w+');
3$operation = LOCK_EX; // LOCK_SH (共有ロック), LOCK_EX (排他ロック), LOCK_UN (ロック解除) のいずれか
4$wouldBlock = 0;      // オプション: ロック試行がブロックされた場合、非ゼロの整数が格納される
5
6$file->flock($operation, $wouldBlock);
7?>

引数(parameters)

int $operation, int &$would_block = null

  • int $operation: 実行するロック操作を指定する整数。PHPのLOCK_SH、LOCK_EX、LOCK_UN、LOCK_NB定数を使用します。
  • int &$would_block = null: オプション。ロックがすぐに取得できなかった場合に真偽値(true/false)が設定されます。

戻り値(return)

bool

ファイルロックの試みが成功した場合はtrueを、失敗した場合はfalseを返します。

サンプルコード

PHP: flock でファイルロックをタイムアウト付きで取得する

1<?php
2
3/**
4 * 指定されたファイルに対する排他ロックを、タイムアウト付きで取得しようと試みます。
5 *
6 * flock は直接的なタイムアウト機能を持たないため、非ブロッキングロック (LOCK_NB) とループ、
7 * そして時間計測を組み合わせて擬似的なタイムアウトを実装します。
8 *
9 * @param string $filePath ロック対象のファイルのパス
10 * @param int $timeoutSeconds ロック取得を試みる最大秒数
11 * @param int $retryIntervalMicroseconds ロック取得に失敗した場合のリトライ間隔 (マイクロ秒)
12 * @return bool ロック取得に成功した場合は true、タイムアウトした場合は false
13 */
14function acquireExclusiveFileLockWithTimeout(
15    string $filePath,
16    int $timeoutSeconds,
17    int $retryIntervalMicroseconds = 100000 // デフォルト: 0.1秒
18): bool {
19    // SplFileObject を使用してファイルを開きます。
20    // 'c+' モードは、ファイルが存在しない場合は作成し、存在する場合は読み書きのために開きます。
21    // ファイルポインタはファイルの先頭に設定されます。
22    try {
23        $file = new SplFileObject($filePath, 'c+');
24    } catch (RuntimeException $e) {
25        echo "エラー: ファイル {$filePath} を開けませんでした: " . $e->getMessage() . PHP_EOL;
26        return false;
27    }
28
29    $startTime = microtime(true);
30    $timeoutTime = $startTime + $timeoutSeconds;
31
32    echo "ファイル {$filePath} のロック取得を試行中... (タイムアウト: {$timeoutSeconds}秒)" . PHP_EOL;
33
34    while (microtime(true) < $timeoutTime) {
35        $wouldBlock = false; // ロックが失敗した場合、他のプロセスがロックしているかどうかを示すフラグ
36        
37        // 排他ロック (LOCK_EX) を非ブロッキングモード (LOCK_NB) で試行します。
38        // LOCK_NB を指定すると、ロックが取得できない場合、即座に false を返し、処理がブロックされません。
39        if ($file->flock(LOCK_EX | LOCK_NB, $wouldBlock)) {
40            echo "ロック取得に成功しました。" . PHP_EOL;
41            
42            // --- ロック取得後の、排他的に行いたい処理をここに記述します ---
43            echo "--- ロックされた領域での作業を開始 ---" . PHP_EOL;
44            // 例: ファイルへの書き込み、データ処理など
45            sleep(1); // 処理に時間がかかると仮定して1秒待機
46            echo "--- ロックされた領域での作業を終了 ---" . PHP_EOL;
47
48            // 作業完了後、ロックを解放します。
49            $file->flock(LOCK_UN);
50            echo "ロックを解放しました。" . PHP_EOL;
51            return true;
52        }
53
54        // ロック取得に失敗したが、$wouldBlock が true の場合、他のプロセスがロックしていることを意味します。
55        if ($wouldBlock) {
56            // 他のプロセスがロックしているため、指定された間隔だけ待機して再試行します。
57            usleep($retryIntervalMicroseconds);
58        } else {
59            // ロックがブロックされたわけではない(例: 無効な引数、ディスクエラーなど)場合、
60            // 予期せぬエラーとして処理を終了します。
61            echo "エラー: ロック取得に予期せぬ問題が発生しました。" . PHP_EOL;
62            return false;
63        }
64    }
65
66    echo "タイムアウトしました。ロックを取得できませんでした。" . PHP_EOL;
67    return false;
68}
69
70// -----------------------------------------------------------------------------
71// サンプルコードの実行部分
72// -----------------------------------------------------------------------------
73
74// 一時ファイルを作成するためのパスを定義します。
75$lockFilePath = sys_get_temp_dir() . '/php_flock_timeout_example.lock';
76
77// 既にファイルが存在する場合は削除し、クリーンな状態で開始します。
78if (file_exists($lockFilePath)) {
79    unlink($lockFilePath);
80}
81
82// ロック取得のタイムアウト秒数とリトライ間隔 (マイクロ秒) を設定します。
83$timeoutSeconds = 5;    // 5秒間ロックを試みます
84$retryInterval = 50000; // 50ミリ秒 (0.05秒) ごとに再試行
85
86echo "ロック対象ファイル: {$lockFilePath}" . PHP_EOL;
87
88// ロック取得関数を呼び出します。
89if (acquireExclusiveFileLockWithTimeout($lockFilePath, $timeoutSeconds, $retryInterval)) {
90    echo "メインプロセス: ロック取得と解放が正常に完了しました。" . PHP_EOL;
91} else {
92    echo "メインプロセス: ロック取得に失敗またはタイムアウトしました。" . PHP_EOL;
93}
94
95// 後処理: 一時ファイルを削除します。
96if (file_exists($lockFilePath)) {
97    unlink($lockFilePath);
98}

PHPのSplFileObject::flockメソッドは、ファイルへの同時アクセスを防ぎ、データの競合を避けるための排他制御(ロック)機能を提供します。このメソッドは、SplFileObjectで開かれたファイルインスタンスに対して呼び出され、ファイルに対するロック操作を実行します。

引数$operationには、排他ロックを示すLOCK_EXや、ロックが取得できない場合に処理を待機させず即座に結果を返す非ブロッキングモードのLOCK_NBなどを組み合わせて指定します。オプション引数$would_blockは参照渡しで、ロック取得に失敗した際に、他のプロセスがすでにファイルをロックしている場合にtrueがセットされます。

flockメソッド自体には直接的なタイムアウト機能は備わっていません。そのため、サンプルコードではLOCK_NBモードでロック取得をループ内で繰り返し試行し、現在の時刻が設定されたタイムアウト時間を超過したかをチェックすることで、擬似的なタイムアウト処理を実装しています。これにより、指定された時間内にロックが取得できなければ処理を中断することが可能です。

ロックの取得に成功するとメソッドはtrueを返し、排他的に行いたい処理を実行できます。処理が完了したら、LOCK_UNを指定してロックを解放する必要があります。ロック取得に失敗した場合はfalseが返されます。この仕組みにより、複数のプロセスが安全にファイルを扱うことができます。

PHPのflock関数には直接的なタイムアウト機能がないため、サンプルコードのようにLOCK_NB(非ブロッキング)とループを組み合わせて、擬似的なタイムアウトを実装している点を理解してください。ロック取得に失敗した際、$would_block引数が他のプロセスによるロックを示しているか確認し、usleepで待機することで、CPUの無駄な消費を防ぎ効率的に利用できます。排他的な処理が完了したら、必ず$file->flock(LOCK_UN)でロックを明示的に解放してください。これを忘れると、他のプロセスが永久にロックを取得できなくなる恐れがあります。SplFileObjectはスクリプト終了時に自動的に閉じられますが、ロックは明示的に解放する習慣をつけましょう。また、ロックファイルのパスとアクセス権限も適切に設定してください。

PHP flockによるファイルロックと排他制御

1<?php
2
3/**
4 * ファイルロックを使用して排他制御を行う処理のサンプル。
5 *
6 * この関数は、指定されたファイルに対して排他ロックを試みます。
7 * 他のプロセスが既にロックを保持している場合、ロックは取得できず、
8 * その理由(ブロックされたこと)を通知します。
9 * これは「php flock 効かない」と感じる状況が、実際にはロックが正常に機能しており、
10 * 別のプロセスによってロックされているために自身がブロックされた、というケースを説明するのに役立ちます。
11 *
12 * 実行方法:
13 * 1. このファイルを保存し、PHP CLIで実行します。例: `php your_script.php`
14 * 2. 複数のターミナルで同時にこのコマンドを実行してみてください。
15 *    ロックを取得したプロセスが3秒間待機する間に、他のプロセスがブロックされる様子を確認できます。
16 *
17 * @param string $filePath ロック対象のファイルのパス
18 */
19function processWithFileLock(string $filePath): void
20{
21    // ファイルを読み書きモード ('c+') で開きます。
22    // 'c+' はファイルが存在しない場合に作成し、存在する場合は内容を切り詰めずに開きます。
23    // flockはファイルがオープンされている必要があります。
24    try {
25        $file = new SplFileObject($filePath, 'c+');
26    } catch (RuntimeException $e) {
27        echo "[" . getmypid() . "] ファイルを開けませんでした: " . $e->getMessage() . PHP_EOL;
28        return;
29    }
30
31    // flockがブロックされたかどうかを示す変数を初期化します。
32    // これは参照渡し (int &$would_block) で、flockメソッドによって値が更新されます。
33    $wouldBlock = false;
34
35    // 排他ロック (LOCK_EX) を非ブロッキングモード (LOCK_NB) で試みます。
36    // LOCK_NB を指定することで、ロックが取得できない場合でも、プロセスは待機せずにすぐに結果を返します。
37    // flockが成功した場合、trueを返します。失敗した場合、falseを返します。
38    if ($file->flock(LOCK_EX | LOCK_NB, $wouldBlock)) {
39        // ロックの取得に成功した場合の処理
40        echo "[" . getmypid() . "] ロックを取得しました。3秒間待機してからファイルに書き込みます..." . PHP_EOL;
41
42        // 他のプロセスが待機する状況をシミュレートするために一時停止します。
43        sleep(3);
44
45        $timestamp = date('Y-m-d H:i:s');
46        $file->fwrite("PID " . getmypid() . " が " . $timestamp . " に書き込みました。" . PHP_EOL);
47
48        echo "[" . getmypid() . "] 書き込み完了。ロックを解放します。" . PHP_EOL;
49
50        // ロックを解放します。
51        // プロセス終了時にも自動的に解放されますが、明示的に解放するのが良い習慣です。
52        $file->flock(LOCK_UN);
53    } elseif ($wouldBlock) {
54        // ロックが取得できず、$wouldBlockがtrueの場合、他のプロセスによってブロックされたことを意味します。
55        // これは「flock 効かない」と感じるかもしれませんが、実際にはロックが正常に機能している証拠です。
56        echo "[" . getmypid() . "] ファイルは別のプロセスによってロックされています。ブロックされました。" . PHP_EOL;
57    } else {
58        // flock が false を返したが、$wouldBlock が false の場合 (通常は発生しにくい、または権限などの問題)
59        echo "[" . getmypid() . "] ロックの取得に失敗しました (原因不明)。" . PHP_EOL;
60    }
61
62    // SplFileObject のオブジェクトがスコープ外に出るか、スクリプトが終了すると、
63    // ファイルは自動的に閉じられ、かけられたロックは解放されます。
64}
65
66// ロックに使用する一時ファイルのパスを生成します。
67$lockFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'php_flock_example.lock';
68
69echo "-----------------------------------------------------" . PHP_EOL;
70echo "サンプルコードを実行中: " . $lockFilePath . PHP_EOL;
71echo "複数のターミナルで同時にこのスクリプトを実行して、排他制御の動作を確認してください。" . PHP_EOL;
72echo "-----------------------------------------------------" . PHP_EOL;
73
74// 排他制御処理を実行
75processWithFileLock($lockFilePath);
76
77echo "[" . getmypid() . "] プロセス終了。" . PHP_EOL;
78

PHPのSplFileObject::flockメソッドは、ファイルに対して排他制御を行うための機能です。このサンプルコードは、一時ファイルを使用して複数のPHPプロセス間で安全なファイルアクセスを実現する方法を示しています。

flockメソッドは、第一引数$operationでロックの種類を指定します。例えば、LOCK_EXは排他ロックを、LOCK_UNはロックの解放を意味します。また、LOCK_NBを組み合わせることで、ロックが取得できない場合にプロセスが待機せず、すぐに結果を返す非ブロッキングモードで動作させることができます。第二引数&$would_blockは参照渡しで、ロック取得に失敗した場合に、他のプロセスによってブロックされたかどうかを真偽値で設定します。メソッドの戻り値は、ロックの取得に成功した場合はtrue、失敗した場合はfalseです。

このサンプルでは、排他ロックを非ブロッキングモードで試みています。ロックの取得に成功したプロセスは、ファイルに書き込みを行うために3秒間待機し、その後ロックを解放します。もしロック取得に失敗し、かつ$would_blocktrueであった場合、それはファイルが既に別のプロセスによってロックされていることを意味します。これは「php flock 効かない」と感じる状況が、実際にはロック機能が正常に動作しており、自身がブロックされた結果であることを示しています。このコードを複数のターミナルで同時に実行することで、排他制御の具体的な動作を確認できます。

php flock 効かないと感じる状況は、実際にはファイルロックが正常に機能し、他のプロセスによってブロックされているケースが多いです。flockは、まずファイルがオープンされている状態で使用する必要があります。サンプルコードのようにLOCK_NB$operationに含めると、ロックが取得できない場合にプロセスが待機せず、すぐに結果を返します。この際、第二引数の&$would_blocktrueになることで、ロックが他のプロセスによってブロックされたと判断できます。処理完了後は、自動解放を待たずにLOCK_UNで明示的にロックを解放する習慣が良いでしょう。

関連コンテンツ