PHPでファイルを安全に操作するために欠かせないflock()
関数について、実践的な例を交えながら詳しく解説します。
flockとは何か?
flock()
はPHPのファイルシステム関数で、複数のプロセスが同時に同じファイルにアクセスする際の競合を防ぐためのファイルロック機構を提供します。
基本構文
bool flock ( resource $handle , int $operation [, int &$wouldblock ] )
$handle
: ファイルポインタリソース(fopen()
で取得)$operation
: 実行するロック操作$wouldblock
: ブロッキング操作の結果を格納する変数(オプション)
主なロック操作定数
LOCK_SH
: 共有ロック(読み取り用)LOCK_EX
: 排他ロック(書き込み用)LOCK_UN
: ロック解除LOCK_NB
: ノンブロッキング(他の定数と組み合わせて使用)
基本的な使用例
例1: 排他ロックによる安全な書き込み
<?php
$file = 'data.txt';
$fp = fopen($file, 'a+');
if (flock($fp, LOCK_EX)) { // 排他ロックを取得
fwrite($fp, "New data line\n");
flock($fp, LOCK_UN); // ロックを解除
} else {
echo "ファイルロックを取得できませんでした";
}
fclose($fp);
例2: 共有ロックによる読み取り
<?php
$file = 'data.txt';
$fp = fopen($file, 'r');
if (flock($fp, LOCK_SH)) { // 共有ロックを取得
$contents = fread($fp, filesize($file));
echo $contents;
flock($fp, LOCK_UN); // ロックを解除
} else {
echo "ファイルロックを取得できませんでした";
}
fclose($fp);
例3: ノンブロッキングロック
<?php
$file = 'data.txt';
$fp = fopen($file, 'w');
$wouldblock = 0;
// ノンブロッキングモードで排他ロックを試みる
if (flock($fp, LOCK_EX | LOCK_NB, $wouldblock)) {
fwrite($fp, "Data written at " . date('Y-m-d H:i:s') . "\n");
flock($fp, LOCK_UN);
echo "ファイルに書き込みました";
} else {
if ($wouldblock) {
echo "ファイルは他のプロセスによってロックされています";
} else {
echo "ロック取得に失敗しました";
}
}
fclose($fp);
実践的なパターン
パターン1: ファイル書き込みの安全なラッパー関数
<?php
function safe_write_file($filename, $data) {
$success = false;
$fp = fopen($filename, 'w');
if ($fp) {
if (flock($fp, LOCK_EX)) {
fwrite($fp, $data);
fflush($fp);
flock($fp, LOCK_UN);
$success = true;
}
fclose($fp);
}
return $success;
}
// 使用例
if (safe_write_file('config.json', json_encode($config_data))) {
echo "設定を保存しました";
} else {
echo "設定の保存に失敗しました";
}
パターン2: ファイルベースのセマフォ実装
<?php
class FileSemaphore {
private $lockFile;
private $handle;
public function __construct($name) {
$this->lockFile = sys_get_temp_dir() . "/lock_{$name}.sem";
}
public function acquire($timeout = 10) {
$this->handle = fopen($this->lockFile, 'w+');
if (!$this->handle) {
throw new Exception("セマフォロックファイルを開けません: {$this->lockFile}");
}
$startTime = time();
$acquired = false;
// タイムアウトまで試行
while ((time() - $startTime) < $timeout) {
if (flock($this->handle, LOCK_EX | LOCK_NB)) {
$acquired = true;
break;
}
usleep(100000); // 0.1秒待機
}
if (!$acquired) {
fclose($this->handle);
throw new Exception("ロック取得タイムアウト");
}
return true;
}
public function release() {
if ($this->handle) {
flock($this->handle, LOCK_UN);
fclose($this->handle);
$this->handle = null;
}
}
public function __destruct() {
$this->release();
}
}
// 使用例
try {
$sem = new FileSemaphore('critical_process');
$sem->acquire();
// クリティカルセクション(排他的な処理)
echo "クリティカルな処理を実行中...\n";
sleep(2);
$sem->release();
} catch (Exception $e) {
echo "エラー: " . $e->getMessage();
}
よくある問題と解決策
問題1: ロックの忘れ解除
<?php
// 問題のあるコード
$fp = fopen('data.txt', 'w');
flock($fp, LOCK_EX);
if (rand(0, 1)) {
// 条件によってはロック解除されない
return;
}
flock($fp, LOCK_UN);
fclose($fp);
// 改善策:try/finallyを使用
$fp = fopen('data.txt', 'w');
try {
flock($fp, LOCK_EX);
// 処理
} finally {
flock($fp, LOCK_UN);
fclose($fp);
}
問題2: NFS上でのロック問題
<?php
// NFS上では信頼性が低い場合がある
// 代替策:独自のロックファイル
function nfs_safe_lock($name) {
$lock_file = "/tmp/lock_{$name}.lock";
$lock_handle = @fopen($lock_file, 'x');
if ($lock_handle) {
fwrite($lock_handle, getmypid());
return $lock_handle;
}
return false;
}
function nfs_safe_unlock($handle, $name) {
fclose($handle);
@unlink("/tmp/lock_{$name}.lock");
}
// 使用例
if ($lock = nfs_safe_lock('my_process')) {
// 処理
nfs_safe_unlock($lock, 'my_process');
}
パフォーマンスとベストプラクティス
- ロックの粒度: 必要な部分だけをロックし、ロック期間を最小限に抑える
- タイムアウト処理: 長時間のロックによるデッドロックを避ける
- エラー処理: ロック失敗や例外発生時も確実にロックを解除する
- 実行環境の考慮: 共有ホスティングやNFS環境では動作が異なる場合がある
flockの制限事項
- Windowsでは一部の機能が制限される
- NFS等の分散ファイルシステムでは信頼性が低い場合がある
- 同一プロセス内の複数スレッド間では機能しない
まとめ
flock()
関数は、PHPでマルチプロセス環境におけるファイル操作を安全に行うための重要なツールです。適切に使用することで、以下のようなメリットがあります:
- データの整合性の確保
- 競合状態(race condition)の防止
- 並行処理の安全な実装
特に高トラフィックのWebアプリケーションやCLIスクリプトでの並行実行時に重要となります。ただし、環境による制限を理解し、適切なエラー処理と組み合わせて使用することが重要です。