2011-08-06 25 views
11

flock()的PHP文檔頁面表明在IIS下使用它並不安全。如果我在任何情況下都不能依賴flock,還有另一種方法可以安全地實現同樣的目標嗎?PHP flock()備選

+0

如果您需要避免讀取長度爲0的文件,'flock()'也很不方便。這是因爲'flock()'只能在文件創建後被調用,所以不可能創建一個新文件並以原子方式寫入。 – rustyx

+0

此外,我讀了強制性文件鎖定在Linux上不推薦使用,所以flock確實不理想(也有一些工作要設置) – Antony

回答

7

在所有想象的可能情況下,沒有其他方法可以安全地達到同樣的效果。這是計算機系統的設計和the job is not trivial for cross-platform code

如果您需要安全使用flock(),請改爲記錄您的應用程序的要求。

或者,您可以創建自己的鎖定機制,但是您必須確保它是原子性的。這意味着,您必須測試該鎖,如果該鎖不存在,則需要確保沒有其他東西可以獲取該鎖之間的鎖時建立該鎖。

這可以通過創建表示鎖的鎖文件來完成,但只有在它不存在的情況下才能完成。不幸的是,PHP沒有提供這樣的功能來創建文件。

或者,您可以創建一個包含mkdir()的目錄並處理結果,因爲目錄創建時它將返回true,如果它已經存在,將返回false

+0

這很完美。我想過鎖定文件,但在PHP中實現它們時遇到了問題。我也想過用行鎖定來設置和取消設置一個「鎖定」標誌的數據庫,但這種方法速度慢並且不穩健。但是,我沒有想過使用一個目錄來代替鎖文件!謝謝! – Matty

+0

但這隻適用於*如果* mkdir()正在返回指定的值。我不知道所有plattform /文件系統組合是否都是*。 – hakre

+0

'mkdir()'行爲正常 - 至少在Linux上。我將在Windows服務器上測試這一點。如果它不能按預期工作,我會回來更新它。 – Matty

1

我的建議是使用mkdir()而不是flock()。這是讀/寫高速緩存顯示差異的真實的例子:

$data = false; 
$cache_file = 'cache/first_last123.inc'; 
$lock_dir = 'cache/first_last123_lock'; 
// read data from cache if no writing process is running 
if (!file_exists($lock_dir)) { 
    // we suppress error messages as the cache file exists in 99,999% of all requests 
    $data = @include $cache_file; 
} 
// cache file not found 
if ($data === false) { 
    // get data from database 
    $data = mysqli_fetch_assoc(mysqli_query($link, "SELECT first, last FROM users WHERE id = 123")); 
    // write data to cache if no writing process is running (race condition safe) 
    // we suppress E_WARNING of mkdir() because it is possible in 0,001% of all requests that the dir already exists after calling file_exists() 
    if (!file_exists($lock_dir) && @mkdir($lock_dir)) { 
     file_put_contents($cache_file, '<?php return ' . var_export($data, true) . '; ?' . '>')) { 
     // remove lock 
     rmdir($lock_dir); 
    } 
} 

現在,我們試圖達到同樣與flock()

$data = false; 
$cache_file = 'cache/first_last123.inc'; 
// we suppress error messages as the cache file exists in 99,999% of all requests 
$fp = @fopen($cache_file, "r"); 
// read data from cache if no writing process is running 
if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) { 
    // we suppress error messages as the cache file exists in 99,999% of all requests 
    $data = @include $cache_file; 
    flock($fp, LOCK_UN); 
} 
// cache file not found 
if (!is_array($data)) { 
    // get data from database 
    $data = mysqli_fetch_assoc(mysqli_query($link, "SELECT first, last FROM users WHERE id = 123")); 
    // write data to cache if no writing process is running (race condition safe) 
    $fp = fopen($cache_file, "c"); 
    if (flock($fp, LOCK_EX | LOCK_NB)) { 
     ftruncate($fp, 0); 
     fwrite($fp, '<?php return ' . var_export($data, true) . '; ?' . '>'); 
     flock($fp, LOCK_UN); 
    } 
} 

的重要組成部分,是LOCK_NB,以避免阻塞所有連續請求:

也可以到LOCK_NB添加爲位掩碼上述 操作之一,如果你不希望flock()阻止WH ile鎖定。

沒有它,代碼會產生一個巨大的瓶頸!

另外一個重要部分是if (!is_array($data)) {。這是因爲$數據可能包含:

  1. array()作爲數據庫查詢
  2. 的失敗include
  3. 或空字符串(race condition

競爭狀態發生false的結果如果第一位訪客執行此行:

$fp = fopen($cache_file, "c"); 

和另一個訪問者一毫秒後執行該行:

if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) { 

這意味着第一訪問者創建空文件,但第二訪問者創建鎖等include返回一個空字符串。

$filename = 'index.html'; 
$loops = 10000; 
$start = microtime(true); 
for ($i = 0; $i < $loops; $i++) { 
    file_exists($filename); 
} 
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL; 
$start = microtime(true); 
for ($i = 0; $i < $loops; $i++) { 
    $fp = @fopen($filename, "r"); 
    flock($fp, LOCK_EX | LOCK_NB); 
} 
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL; 

結果:

file_exists: 0.00949 
fopen/flock: 0.06401 

附註:

所以,你通過使用mkdir()和7倍速度更快,也看到了可避免很多陷阱正如你所看到的,我在mkdir()之前使用了file_exists()。這是因爲my tests(德語)單獨使用mkdir()會導致瓶頸。

+1

「如果鎖定文件並且腳本在寫入時停止到一個文件鎖被釋放。「 - 如果您使用鎖來控制正在運行的進程對共享資源的訪問權限,那麼這是一個非常理想的功能。不用擔心在流程死亡後掛起的鎖,可以簡化處理過程。像往常一樣,很大程度上取決於你最終想達到的目標。 – Jason

+1

那裏有很多[@運營商](http://php.net/manual/en/language.operators.errorcontrol.php)的使用,這可能會以各種方式炸燬,你永遠不會知道發生了什麼。 –

+0

@JosipRodin我重寫了我的答案。我仍然使用@運算符,但添加了解釋。 – mgutt

2

您可以實現文件鎖 - 開鎖基於的mkdir在你的讀/寫操作模式,因爲這是原子彈,非常快。我已經強調過這一點,並且不像mgutt沒有發現瓶頸。儘管如此,你必須關心僵局,這可能是mgutt經歷的。當兩次鎖定嘗試彼此保持等待時,就會發生死鎖。它可以通過鎖定嘗試的隨機時間間隔來彌補。像這樣:

// call this always before reading or writing to your filepath in concurrent situations 
function lockFile($filepath){ 
    clearstatcache(); 
    $lockname=$filepath.".lock"; 
    // if the lock already exists, get its age: 
    [email protected]($lockname); 
    // attempt to lock, this is the really important atomic action: 
    while ([email protected]($lockname)){ 
     if ($life) 
      if ((time()-$life)>120){ 
       //release old locks 
       rmdir($lockname); 
       $life=false; 
     } 
     usleep(rand(50000,200000));//wait random time before trying again 
    } 
} 

然後在你的文件中的文件路徑工作,當你做,請致電:

function unlockFile($filepath){ 
    $unlockname= $filepath.".lock"; 
    return @rmdir($unlockname); 
} 

我選擇了刪除舊鎖,在最大PHP執行時間以及之後腳本在解鎖前退出。更好的方法是在腳本失敗時總是刪除鎖。這有一條幹淨的道路,但我已經忘記了。

0

我很欣賞這個問題已經過了幾年,但我有點感覺到,一個可行的示例/替換羣可能值得建設。我已經根據其他答案,但對於純粹希望替換羣體功能的人(而不是同時寫入文件(儘管這確實反映了PHP手冊中的羣集示例)),我相信以下內容就足夠了:

function my_flock ($path,$release = false){ 
    if ($release){ 
     @rmdir($path); 
    } else { 
     return !file_exists($path) && @mkdir($path); 
    } 
}