LOCK_EX | LOCK_NB 立即返回失敗不等待。<?php
function writeLog($file, $message) {
$fp = fopen($file, 'ab'); // 追加二進制,指針在末尾
if (!$fp) return false;
// 獲取排他鎖(阻塞等待)
if (flock($fp, LOCK_EX)) {
fwrite($fp, $message . "\n");
flock($fp, LOCK_UN); // 釋放鎖
} else {
fclose($fp);
return false;
}
fclose($fp);
return true;
}
// 使用
writeLog('/var/log/app.log', date('c') . ' User login: user123');
?>
<?php
function readLog($file, $lines = 50) {
$fp = fopen($file, 'rb');
if (!$fp) return [];
if (flock($fp, LOCK_SH)) {
// 讀取最後 N 行(簡化版,大文件用 fseek 反向讀取)
$content = stream_get_contents($fp);
flock($fp, LOCK_UN);
fclose($fp);
$allLines = explode("\n", rtrim($content));
return array_slice($allLines, -$lines);
}
fclose($fp);
return [];
}
?>
避免對業務文件直接加鎖導致讀取阻塞,創建專用 .lock 文件。
<?php
function acquireLock($lockFile, $timeout = 10) {
$start = time();
$fp = fopen($lockFile, 'c+'); // 打開不截斷,創建不存在
if (!$fp) return false;
while (true) {
if (flock($fp, LOCK_EX | LOCK_NB)) {
return $fp; // 返回句柄,調用者負責釋放
}
if (time() - $start > $timeout) {
fclose($fp);
return false;
}
usleep(100000); // 100ms 重試
}
}
function releaseLock($fp) {
flock($fp, LOCK_UN);
fclose($fp);
}
// 業務代碼
$lockFp = acquireLock('/tmp/order_process.lock');
if ($lockFp) {
// 執行關鍵業務:扣庫存、寫訂單、發MQ
processOrder($orderId);
releaseLock($lockFp);
} else {
throw new Exception('獲取鎖超時,訂單處理繁忙');
}
?>
| 問題 | 說明 | 解決/規避 |
|---|---|---|
| NFS/網絡文件系統 | flock 不支持 NFS、SMB 等 | 用數據庫行鎖、Redis 分布式鎖、Consul/etcd |
| 多線程 SAPI (ISAPI, PHP-FPM 非預fork) | 同進程不同線程間 flock 不互斥 | PHP-FPM 預fork模式無此問題;線程模式用信號量 sem_* 或避免 |
| Windows 強制鎖 vs 建議鎖 | Windows flock 為強制鎖,其他進程無法讀寫 | 跨平台代碼測試行為差異 |
| 句柄關閉自動釋放 | fclose、腳本結束會釋放 | 確保異常分支也 fclose,用 try/finally 或 register_shutdown_function |
| 大文件寫入性能 | 頻繁加鎖寫入慢 | 批量緩衝寫入、異步寫入隊列、單獨日誌服務 |
flock + 獨立鎖文件(如上)。flock() 是 PHP 單機文件級併發控制的基石。掌握 LOCK_SH/LOCK_EX/LOCK_UN/LOCK_NB 四種模式,結合獨立鎖文件模式保護業務數據完整性。分布式場景務必升級為 Redis/數據庫/協調服務鎖。