在高併發環境下,PHP 腳本對共享資源 (檔案、快取、資料庫記錄) 的寫入操作必須保證原子性。PHP 提供 flock() 系統呼叫實現檔案層面的建議性鎖定。本文提供生產級可用的 lock_thisfile() / unlock_thisfile() 封裝函數,支援阻塞/非阻塞模式、自動清理鎖檔、完整錯誤處理,並附上使用範例與原理解析。
<?php
/**
* 取得獨佔鎖
* @param string $tmpFileStr 鎖檔基礎名稱 (將自動加 .lock 後綴)
* @param bool $locktype true=阻塞等待, false=非阻塞立即返回
* @return resource|false 成功回傳檔案指標, 失敗回傳 false
*/
function lock_thisfile($tmpFileStr, $locktype = false) {
if ($locktype === false) {
$locktype = LOCK_EX | LOCK_NB; // 非阻塞獨佔鎖
} else {
$locktype = LOCK_EX; // 阻塞獨佔鎖
}
$can_write = 0;
$lockfp = @fopen($tmpFileStr . ".lock", "w");
if ($lockfp) {
$can_write = @flock($lockfp, $locktype);
}
if ($can_write) {
return $lockfp;
} else {
if ($lockfp) {
@fclose($lockfp);
@unlink($tmpFileStr . ".lock");
}
return false;
}
}
/**
* 釋放鎖
* @param resource $fp lock_thisfile 回傳的檔案指標
* @param string $tmpFileStr 鎖檔基礎名稱
*/
function unlock_thisfile($fp, $tmpFileStr) {
@flock($fp, LOCK_UN);
@fclose($fp);
@fclose($fp); // 雙重關閉保險
@unlink($tmpFileStr . ".lock");
}
?>
<?php
$tmpFileStr = "/tmp/mylock.loc";
// 阻塞模式:等待直到取得鎖
$lockhandle = lock_thisfile($tmpFileStr, true);
if ($lockhandle) {
// 執行需獨佔的關鍵區段
// 例如:寫入檔案、更新快取、產生報表、庫存扣減等
// ... 業務邏輯 ...
// 釋放鎖
unlock_thisfile($lockhandle, $tmpFileStr);
} else {
echo "無法取得鎖,資源忙碌中";
}
?>
| 常數 | 說明 |
|---|---|
LOCK_SH | 共享鎖 (讀鎖),多進程可同時持有 |
LOCK_EX | 獨佔鎖 (寫鎖),同一時間僅一進程持有 |
LOCK_UN | 釋放鎖 |
LOCK_NB | 非阻塞模式,無法取得鎖立即返回 false (需配合 LOCK_EX\|LOCK_SH 使用) |
flock() 的進程間協作,不阻止未檢查鎖的進程讀寫flock() 同一檔案會成功 (遞迴鎖)flock() 釋放後檔案指標可能仍保持鎖狀態fclose() 確保檔案描述符徹底關閉unlink() 移除鎖檔避免殘留佔用磁碟空間function getData($key) {
$cacheFile = "/tmp/cache_{$key}.dat";
$lockFile = "/tmp/cache_{$key}.lock";
// 嘗試讀取快取
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
return file_get_contents($cacheFile);
}
// 快取失效,嘗試取得重建鎖
$fp = lock_thisfile($lockFile, false); // 非阻塞
if ($fp) {
// 取得鎖,負責重建快取
$data = rebuildExpensiveData($key);
file_put_contents($cacheFile, $data);
unlock_thisfile($fp, $lockFile);
return $data;
} else {
// 未取得鎖,返回舊快取 (即使過期) 或等待
return file_exists($cacheFile) ? file_get_contents($cacheFile) : null;
}
}
$lock = lock_thisfile("/var/lock/daily_report", false);
if (!$lock) {
die("上一次任務尚未完成,跳過本次執行");
}
// 執行每日報表生成...
unlock_thisfile($lock, "/var/lock/daily_report");
flock() 在 NFS 上可靠性較差,建議使用資料庫行鎖或 Redis 分散式鎖替代flock() 但行為與 Linux 不完全一致,測試驗證為主mutex 而非檔案鎖PHP flock() 搭配封裝函數提供輕量級進程同步機制。核心模式:開啟鎖檔 → 獨佔鎖定 → 執行關鍵區段 → 釋放鎖 → 刪除鎖檔。非阻塞模式適合「搶佔式」場景 (快取重建、排程防重);阻塞模式適合「強一致性」場景 (庫存扣減、檔案追加)。生產環境建議配合監控告警,防止死鎖或鎖殘留。