在高併發的 PHP 應用中,多個進程同時讀寫同一個文件是常見且棘手的問題。若缺乏妥善的鎖定機制,極易導致數據損毀或讀取到髒數據。本文將透過實際代碼示範,如何利用 flock() 函數實現可靠的文件獨佔鎖(寫入)與共享鎖(讀取),並結合重試策略提升穩定性。
我們將鎖定、重試、讀寫、釋放等步驟封裝為兩個獨立函數,方便在項目中複用。
writeData()<?php
/**
* 安全寫入數據到文件(獨佔鎖)
* @param string $path 文件路徑
* @param string $mode fopen 模式,建議 'a+' 或 'w+'
* @param string $data 待寫入內容
* @return bool 成功返回 true,超時失敗返回 false
*/
function writeData($path, $mode, $data) {
$fp = fopen($path, $mode);
if (!$fp) return false;
$retries = 0;
$max_retries = 100;
// 循環嘗試獲取獨佔鎖 (LOCK_EX)
do {
if ($retries > 0) {
// 避免併發衝突,隨機微秒級延遲
usleep(rand(1, 10000));
}
$retries++;
} while (!flock($fp, LOCK_EX) && $retries <= $max_retries);
if ($retries > $max_retries) {
fclose($fp);
return false; // 獲取鎖超時
}
// 寫入數據並換行
fwrite($fp, $data . "\r\n");
// 釋放鎖並關閉句柄
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
?>
readData()<?php
/**
* 安全從文件讀取全部內容(共享鎖)
* @param string $path 文件路徑
* @param string $mode fopen 模式,建議 'r' 或 'r+'
* @return string|false 文件內容或失敗 false
*/
function readData($path, $mode) {
$fp = fopen($path, $mode);
if (!$fp) return false;
$retries = 0;
$max_retries = 100;
// 循環嘗試獲取共享鎖 (LOCK_SH)
do {
if ($retries > 0) {
usleep(rand(1, 10000));
}
$retries++;
} while (!flock($fp, LOCK_SH) && $retries <= $max_retries);
if ($retries > $max_retries) {
fclose($fp);
return false;
}
$contents = '';
// 分塊讀取,避免大文件佔用過多記憶體
while (!feof($fp)) {
$contents .= fread($fp, 8192);
}
flock($fp, LOCK_UN);
fclose($fp);
return $contents;
}
?>
LOCK_EX(獨佔鎖),同一時間僅允許一個進程持有;讀取使用 LOCK_SH(共享鎖),允許多個進程同時讀取,但會阻塞寫入進程。usleep(rand(1, 10000)) 隨機延遲,配合最大重試次數(示例為 100),有效緩解「羊群效應」,避免進程在高併發下持續空轉佔用 CPU。flock($fp, LOCK_UN) 後調用 fclose($fp),確保文件句柄及時歸還系統,防止句柄洩漏。<?php
// 寫入測試
writeData('D:/webServer/demo.txt', 'a+', 'this is a demo');
// 讀取測試(注意:原示例路徑有誤,此處修正為完整文件路徑)
echo readData('D:/webServer/demo.txt', 'r');
?>
提示:Windows 系統下 flock() 為強制鎖定,Linux 下則為建議性鎖定(需所有進程配合使用)。若項目部署於分佈式環境或需跨進程/跨機器協作,建議改用 Redis、Memcached 或數據庫行級鎖等更健壯的分佈式鎖方案。