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

PHP 檔案鎖定機制實作:flock 獨佔鎖與非阻塞模式完整教學

     編輯:PHP 相關知識

在高併發環境下,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 "無法取得鎖,資源忙碌中";
}
?>

關鍵技術細節解析

1. 鎖類型常數

常數說明
LOCK_SH共享鎖 (讀鎖),多進程可同時持有
LOCK_EX獨佔鎖 (寫鎖),同一時間僅一進程持有
LOCK_UN釋放鎖
LOCK_NB非阻塞模式,無法取得鎖立即返回 false (需配合 LOCK_EX\|LOCK_SH 使用)

2. 建議性鎖特性

  • 建議性:僅限制呼叫 flock() 的進程間協作,不阻止未檢查鎖的進程讀寫
  • 進程隔離:同一進程多次 flock() 同一檔案會成功 (遞迴鎖)
  • 自動釋放:進程結束或檔案指標關閉時自動釋放

3. 雙重 fclose 與 unlink 設計理由

  • 某些系統 (特別是 NFS) 上 flock() 釋放後檔案指標可能仍保持鎖狀態
  • 雙重 fclose() 確保檔案描述符徹底關閉
  • unlink() 移除鎖檔避免殘留佔用磁碟空間

進階應用場景

1. 快取失效重建防雪崩

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;
    }
}

2. 排程任務防重複執行

$lock = lock_thisfile("/var/lock/daily_report", false);
if (!$lock) {
    die("上一次任務尚未完成,跳過本次執行");
}
// 執行每日報表生成...
unlock_thisfile($lock, "/var/lock/daily_report");

注意事項與限制

  • NFS 檔案系統:flock() 在 NFS 上可靠性較差,建議使用資料庫行鎖或 Redis 分散式鎖替代
  • Windows 平台:支援 flock() 但行為與 Linux 不完全一致,測試驗證為主
  • 鎖檔目錄權限:確保 PHP 進程對鎖檔目錄有寫入/刪除權限
  • 非跨進程同步:同一進程內多執行緒 (pthreads) 需用 mutex 而非檔案鎖

總結

PHP flock() 搭配封裝函數提供輕量級進程同步機制。核心模式:開啟鎖檔 → 獨佔鎖定 → 執行關鍵區段 → 釋放鎖 → 刪除鎖檔。非阻塞模式適合「搶佔式」場景 (快取重建、排程防重);阻塞模式適合「強一致性」場景 (庫存扣減、檔案追加)。生產環境建議配合監控告警,防止死鎖或鎖殘留。

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