首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP flock() 文件鎖實戰:併發寫入與讀寫分離

     編輯:PHP 相關知識

核心概念

  • 共享鎖 (LOCK_SH):讀鎖,多進程可同時持有,寫進程阻塞。
  • 排他鎖 (LOCK_EX):寫鎖,同一時間只能一個進程持有,其他讀寫均阻塞。
  • 釋放鎖 (LOCK_UN):手動釋放,或關閉文件句柄/腳本結束自動釋放。
  • 非阻塞 (LOCK_NB):配合 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 + 獨立鎖文件(如上)。
  • 分布式/集群:Redis SETNX + Lua 腳本、Redisson、etcd/Consul 鎖、數據庫 SELECT FOR UPDATE。
  • 消息隊列:將寫入操作序列化進 Kafka/RabbitMQ,單消費者寫入,天然避免併發衝突。

總結

flock() 是 PHP 單機文件級併發控制的基石。掌握 LOCK_SH/LOCK_EX/LOCK_UN/LOCK_NB 四種模式,結合獨立鎖文件模式保護業務數據完整性。分布式場景務必升級為 Redis/數據庫/協調服務鎖。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved