PHP Conference Japan 2024

flock

(PHP 4, PHP 5, PHP 7, PHP 8)

flock可移植的諮詢式檔案鎖定

描述

flock(resource $stream, int $operation, int &$would_block = null): bool

flock() 允許您執行簡單的讀取器/寫入器模型,該模型幾乎可在每個平台(包括大多數 Unix 衍生產品,甚至 Windows)上使用。

鎖定也會由 fclose() 釋放,或當 stream 被垃圾回收時釋放。

PHP 支援以諮詢方式鎖定完整檔案的可移植方式(這表示所有存取程式都必須使用相同的鎖定方式,否則將無法運作)。依預設,此函式將會封鎖,直到取得要求的鎖定;這可以使用下方文件中記載的 LOCK_NB 選項來控制。

參數

stream

通常使用 fopen() 建立的檔案系統指標 resource

operation

operation 是下列其中一個

  • LOCK_SH 以取得共享鎖定(讀取器)。
  • LOCK_EX 以取得獨佔鎖定(寫入器)。
  • LOCK_UN 以釋放鎖定(共享或獨佔)。

如果 flock() 在鎖定嘗試期間不應封鎖,也可以將 LOCK_NB 作為位元遮罩新增至上述其中一個作業。

would_block

如果鎖定將會封鎖 (EWOULDBLOCK errno 條件),則可選的第三個引數會設為 1。

傳回值

成功時傳回 true,失敗時傳回 false

範例

範例 #1 flock() 範例

<?php

$fp
= fopen("/tmp/lock.txt", "r+");

if (
flock($fp, LOCK_EX)) { // 取得獨佔鎖定
ftruncate($fp, 0); // 截斷檔案
fwrite($fp, "在這裡寫入一些內容\n");
fflush($fp); // 在釋放鎖定之前,清除輸出
flock($fp, LOCK_UN); // 釋放鎖定
} else {
echo
"無法取得鎖定!";
}

fclose($fp);

?>

範例 #2 使用 LOCK_NB 選項的 flock()

<?php
$fp
= fopen('/tmp/lock.txt', 'r+');

/* 在 LOCK_EX 作業上啟用 LOCK_NB 選項 */
if(!flock($fp, LOCK_EX | LOCK_NB)) {
echo
'無法取得鎖定';
exit(-
1);
}

/* ... */

fclose($fp);
?>

附註

附註:

在 Windows 上,flock() 使用強制鎖定,而不是諮詢式鎖定。Linux 和 System V 型作業系統也透過 fcntl() 系統呼叫支援的常用機制來支援強制鎖定:也就是說,如果相關檔案設定了 setgid 權限位元,並清除了群組執行位元。在 Linux 上,檔案系統也需要使用 mand 選項來掛載才能運作。

附註:

由於 flock() 需要檔案指標,您可能必須使用特殊的鎖定檔案來保護對您打算在寫入模式中開啟(使用 fopen() 的 "w" 或 "w+" 引數)來截斷的檔案的存取。

附註:

只能用於 fopen() 針對本機檔案傳回的檔案指標,或指向實作 streamWrapper::stream_lock() 方法的使用者空間串流的檔案指標。

警告

在後續程式碼中指派另一個值給 stream 引數將會釋放鎖定。

警告

在某些作業系統上,flock() 是在程序層級實作。當使用多執行緒伺服器 API 時,您可能無法依賴 flock() 來保護檔案,使其免受在相同伺服器執行個體的平行執行緒中執行的其他 PHP 指令碼影響!

在過時的檔案系統(例如 FAT 及其衍生產品)上,不支援 flock(),因此在這些環境下將一律傳回 false

附註:

在 Windows 上,如果鎖定程序第二次開啟檔案,則在解除鎖定檔案之前,無法透過此第二個控制代碼來存取檔案。

新增附註

使用者貢獻的附註 39 個附註

170
Antti Haapala
17 年前
所提供的文件含糊不清、模稜兩可且缺乏,且使用者評論包含錯誤的資訊!flock 函式遵循具有相同名稱的 Unix 系統呼叫的語意。Flock 僅利用諮詢式鎖定;也就是說,其他程序可能會完全忽略鎖定;它只會影響那些呼叫 flock 呼叫的程序。

LOCK_SH 表示共享鎖定。任意數量的程序可以同時擁有共享鎖定。它通常稱為讀取器鎖定。

LOCK_EX 表示獨佔鎖定。一次只有一個程序可以擁有給定檔案的獨佔鎖定。

如果檔案已在另一個程序中使用 LOCK_SH 鎖定,則使用 LOCK_SH 的 flock 將會成功。使用 LOCK_EX 的 flock 將會封鎖,直到所有讀取器鎖定都已釋放。

如果檔案已在另一個程序中使用 LOCK_EX 鎖定,則呼叫將會封鎖,直到所有其他鎖定都已釋放。

但是,如果您在您擁有鎖定的檔案上呼叫 flock,它將會嘗試變更它。因此:flock(LOCK_EX) 之後接 flock(LOCK_SH) 將會取得共享鎖定,而不是「讀寫」鎖定。
3
ravenswd at gmail dot com
9 年前
請注意,範例 #1 包含錯誤:ftruncate()「不會」將檔案指標重設為檔案的開頭。您需要執行後續的 rewind() 呼叫。我了解 ftruncate 頁面確實有提到這一點,但如果有人複製上述範例 (就像我一樣),則除非他們修正此問題,否則他們的程式將無法正常運作。
4
Antti Haapala
17 年前
有關 flock 的進一步資訊:如果訊號傳遞至程序,則不會重新啟動系統,因此如果發生 SIGALRM、SIGFPE 或其他情況,flock 將會很高興地傳回 false。
11
jlh at gmx dot ch
7 年前
即使自 5.3.2 版本以來,PHP 不再明確執行釋放鎖定的動作(相關文件對此的說明非常令人困惑),當檔案關閉時,系統無論如何都會釋放鎖定。

然而,我曾在一個 apache/PHP 伺服器上遇到一種情況,PHP 中的記憶體不足錯誤導致檔案控制代碼無法關閉,因此即使 PHP 執行已結束且進程已返回 apache 以服務其他請求,鎖定仍被保留。鎖定會一直保持活動狀態,直到 apache 回收這些進程。

這種缺乏適當清除的機制,基本上使 flock() 完全不可靠。
12
forzi at mail333 dot com
9 年前
建立鎖定檔案的簡單輔助工具

<?php

class FileLocker {
protected static
$loc_files = array();

public static function
lockFile($file_name, $wait = false) {
$loc_file = fopen($file_name, 'c');
if ( !
$loc_file ) {
throw new
\Exception('無法建立鎖定檔案!');
}
if (
$wait ) {
$lock = flock($loc_file, LOCK_EX);
} else {
$lock = flock($loc_file, LOCK_EX | LOCK_NB);
}
if (
$lock ) {
self::$loc_files[$file_name] = $loc_file;
fprintf($loc_file, "%s\n", getmypid());
return
$loc_file;
} else if (
$wait ) {
throw new
\Exception('無法鎖定檔案!');
} else {
return
false;
}
}

public static function
unlockFile($file_name) {
fclose(self::$loc_files[$file_name]);
@
unlink($file_name);
unset(
self::$loc_files[$file_name]);
}

}

if ( !
FileLocker::lockFile('/tmp/1.lock') ) {
echo
"無法鎖定檔案\n";
die();
}
sleep(10);
FileLocker::unlockFile('/tmp/1.lock');
echo
"一切正常\n";
14
webmaster at bitpush dot com
14 年前
關於 PHP 5.3.2 中鎖定檔案的變更

在沒有詳細研究 PHP 原始碼的情況下,當呼叫 PHP 函數 fclose() 時,情況似乎如下:

在 5.3.2 之前的版本中,PHP 會檢查檔案是否已鎖定,然後釋放鎖定,最後關閉檔案。

從 5.3.2 版本開始,PHP 僅關閉檔案。

但請注意,當檔案關閉時,作業系統會自動釋放鎖定。因此,呼叫 fclose() 仍然會釋放鎖定(這已在 PHP 5.3.2、Linux、x64 上進行測試)。
11
Fernando Gabrieli fgabrieli at gmail
15 年前
當寫入檔案時,您應該避免使用 w+,因為它會在鎖定之前清除檔案內容

如果您需要再次寫入整個檔案,您可以改用以下方法:

<?php
$fp
= fopen('yourfile.txt', 'a') ;

if (
flock($fp, LOCK_EX)) {
ftruncate($fp, 0) ; // <-- 這將清除內容,如同 'w+'

fputs($fp, 'test string') ;

flock($fp, LOCK_UN) ;
}

fclose($fp) ;
?>

最好的問候,
Fernando Gabrieli
17
mdessaintes at gmail dot com
12 年前
我剛花了(又)一些時間來理解為什麼使用 file_get_contents() 讀取檔案會返回空字串 "" 或 array(),而該檔案明明存在且內容不為空。

事實上,我在寫入檔案時(file_put_contents 的第三個參數)鎖定了檔案,但在讀取檔案時沒有檢查檔案是否已鎖定(而且檔案被頻繁存取)。

因此,請注意 file_get_contents()、file() 和其他可能的 PHP 檔案函數會返回空數據,就好像檔案內容是空字串一樣。

為了避免這個問題,您必須在讀取檔案之前對檔案設定 LOCK_SH(然後在鎖定時等待)。

類似這樣:

<?php
public static function getContents($path, $waitIfLocked = true) {
if(!
file_exists($path)) {
throw new
Exception('檔案 "'.$path.'" 不存在');
}
else {
$fo = fopen($path, 'r');
$locked = flock($fo, LOCK_SH, $waitIfLocked);

if(!
$locked) {
return
false;
}
else {
$cts = file_get_contents($path);

flock($fo, LOCK_UN);
fclose($fo);

return
$cts;
}
}
}
?>

自行測試的程式碼

abc.txt
someText

file.php
<?php
$fo
= fopen('abc.txt', 'r+');

flock($fo, LOCK_EX);
sleep(10);
flock($fo, LOCK_UN);
?>

file2.php
<?php
var_dump
(file_get_contents('abc.txt'));
var_dump(file('abc.txt'));
?>

然後執行 file.php 並在 10 秒內切換到 file2.php,看看之前/之後的差異
11
dejangex at yahoo dot com
14 年前
實際上,使用 usleep 的 while 迴圈沒有任何用處。我的測試顯示如下:

<?php
//這裡有一些程式碼
flock($file_handle, LOCK_EX) // <- 您的程式碼會在這裡暫停,直到您在無限時間內或直到您的腳本超時為止取得鎖定
//這裡有一些程式碼
?>

這實際上會檢查鎖定,而不會暫停,然後它會睡眠

<?php
//程式碼寫在這裡
while (!flock($file_handle, LOCK_EX | LOCK_NB)) {
// 鎖定未取得,稍後再試:
usleep(round(rand(0, 100)*1000)) // 0-100 毫秒
}
//鎖定已取得
//剩餘的程式碼
?>

問題是,如果你的網站繁忙且有大量的鎖定,這個 while 迴圈可能需要一段時間才能取得鎖定。不使用 LOCK_NB 的鎖定會更加持久,它會等待鎖定直到取得為止。除非腳本超時或其他情況,否則幾乎可以保證檔案會被鎖定。

考慮以下兩個腳本:第一個腳本先執行,第二個腳本在第一個腳本執行 5 秒後執行。

<?php
//第一個腳本
$file_handle = fopen('file.test', 'r+');
flock($file_handle, LOCK_EX); //鎖定檔案
sleep(10); //睡眠 10 秒
fclose($file_handle); //關閉並解鎖檔案
?>

<?php
//第二個腳本
$file_handle = fopen('file.test', 'r+');
flock($file_handle, LOCK_EX); //鎖定檔案
fclose($file_handle); //關閉並解鎖檔案
?>

如果你先執行第一個腳本,然後執行第二個腳本,第二個腳本會等待直到第一個腳本完成。一旦第一個腳本完成,第二個腳本就會取得鎖定並完成執行。如果在第一個腳本執行時,你在第二個腳本中使用 `flock($file_handle, LOCK_EX | LOCK_NB)`,它會立即完成執行,而你不會取得鎖定。
8
metamarkers at gmail dot com
11 年前
你可以將鎖定包裝成一個物件,使其成為基於範圍的鎖定。當鎖定物件不再被引用時,例如當它被取消設定或擁有者返回時,解構函數會調用解鎖。

這樣你就可以直接建立一個鎖定物件並忘記它。

<?php
class lock {

private
$handle;

public static function
read ( $handle ) {
$lock = new static();
$lock->handle = $handle;
return
flock($handle,LOCK_SH) ? $lock : false;
}

public static function
write ( $handle ) {
$lock = new static();
$lock->handle = $handle;
return
flock($handle,LOCK_EX) ? $lock : false;
}

public function
__destruct ( ) {
flock($this->handle,LOCK_UN);
}

}
?>
4
joel[at_sign]purerave.com
21 年前
我發現,如果你使用 'w' 或 'w+'("將檔案指標放在檔案的開頭並將檔案截斷為零長度")開啟一個目前鎖定的檔案,那麼當鎖定釋放且檔案可用時,它不會截斷該檔案。

我用來測試它的範例
<?php
// a.php
$fp = fopen( "/tmp/lock.txt", "w+" );
flock( $fp, LOCK_EX ); // 獨佔鎖定

$steps = 10;
// 寫入檔案
for ($i=0; $i< $steps; $i++) {
fwrite($fp, 'a '.time().' test '. $i."\n");
sleep(1);
}
flock( $fp, LOCK_UN ); // 釋放鎖定
fclose( $fp );
?>

----------
<?php
// b.php

$fp = fopen( "/tmp/lock.txt", "w+" );
flock( $fp, LOCK_EX ); // 獨佔鎖定

// 此處需要 ftruncate($fp, 0)! <----

$steps = 5;
// 寫入檔案
for ($i=0; $i< $steps; $i++) {
fwrite($fp, 'b '.time().' test '. $i."\n");
sleep(1);
}
flock( $fp, LOCK_UN ); // 釋放鎖定
fclose( $fp );
?>

載入 a.php,然後立即載入 b.php 將會產生
b 1054075769 test 0
b 1054075770 test 1
b 1054075771 test 2
b 1054075772 test 3
b 1054075773 test 4
a 1054075764 test 5
a 1054075765 test 6
a 1054075766 test 7
a 1054075767 test 8
a 1054075768 test 9

如你所見,如果檔案可以立即使用,b.php 不會像 w+ 所暗示的那樣截斷檔案。而只是將指標移動到檔案的開頭。如果 b.php 在 a.php 完成後載入,那麼檔案中就不會有 "a ..." 行,因為它會被截斷。

要修正這個問題,你必須在 flock 之後加入 ftruncate($fp, 0)。

但是 'r+' 和 'a' 似乎可以正常運作。
6
John dot wellesz at teaser dot fr
16 年前
我只想補充一個關於在 NFS 上進行原子鎖定的注意事項,只有兩種方法
方法

- 1(最穩健但也最複雜)- 這是使用 link() 來建立一個
到你要鎖定的檔案的硬連結(當然是在同一個檔案系統上)。
(在大多數 NFS 實作中,Link() 是原子的)

一旦你用一個唯一的隨機
產生的名稱建立一個硬連結(而不是符號連結),對其呼叫 stat() 並計算連結數 (nlink),如果
只有 2 個,那麼檔案就被鎖定了。

如果有兩個以上,你必須 unlink() 你剛剛建立的連結並
用新的唯一名稱建立一個新的連結(否則 NFS 會使用它的快取,並且 stat
會返回錯誤的數據),然後對新的連結呼叫 stat() 並再次測試連結的數量,
重複此操作直到你取得鎖定為止。

你必須在 link() 嘗試之間使用 usleep(),並使用固定 + 隨機
睡眠值,以避免死鎖情況(link() 和 unlink() 可能是原子的,
但不是瞬時的)

另請注意,當你透過 NFS 取消連結一個檔案時,如果 NFS 認為該檔案
仍在使用中,它將會建立一個 .nfs 連結到這個檔案,直到它意識到
檔案不再使用... 錯誤的時機可能會產生數千個
這些檔案和死鎖情況。因此,當發生死鎖情況
或如果你的 stat() 命令返回非常多的連結時,你必須
在建立連結的同一個目錄中尋找 .nfs 檔案並取消連結
所有你找到的 .nfs 檔案(有時 NFS 需要時間才能移除它們)

- 2(最簡單)- 第二種方法是使用鎖定伺服器和鎖定守護進程
在每個客戶端上,將鎖定請求轉發到伺服器...(這比較
比第一種方法更危險,因為守護進程可能會被殺死...)

以下是我建立的透過 NFS 進行原子鎖定的函數,供參考
(這個函數至少已在生產環境中使用 4 年了),這僅供
參考,因為它使用了許多外部函數來完成其工作,但你可以看到
原理

http://pastey.net/85793
1
Joby <god at NOSPAMPLEASE dot greentinted dot net>
20 年前
我正在考慮一個好方法,確保不會遺失任何資料,那就是建立一個緩衝目錄,可以儲存要寫入檔案的指令。然後,每當檔案明確解鎖時,單次執行就可以迴圈處理該目錄中的每個檔案,並將指示的變更套用到檔案。

我正在為一個基於純文字檔案的資料庫撰寫這個功能。它的運作方式是,每當發出命令(addline、removeline、editline)時,該命令會儲存在一個純文字檔案中,該檔案儲存在一個名為要編輯的檔案名稱縮寫版本的資料夾中,並以時間和隨機數字命名。該檔案中有一組標準化的命令,定義要對哪個檔案執行什麼操作(例如「file: SecuraLog/index_uid」換行「editline: 14」)。

每次執行都會檢查該目錄中的每個資料夾是否有檔案,並花費一定的時間(我不知道多久,也許 1-2 秒)將待處理的變更套用到已解鎖的檔案。這樣就不會遺失任何變更(例如,人員 1 與人員 2 同時進行變更,人員 1 剛好輸了競爭,導致他們變更的檔案版本被人員 2 的版本覆寫),並且開啟空的開啟檔案時也不會有問題。
3
markus at malkusch dot de
8 年前
這是 UNIX 的逾時範本

<?php
pcntl_signal
(SIGALRM, function() {});
pcntl_alarm(3);
try {
if (!
flock($handle, LOCK_EX)) {
throw new
\Exception("Timeout");
}
} finally {
pcntl_alarm(0);
pcntl_signal_dispatch();
pcntl_signal(SIGALRM, SIG_DFL);
}
?>
6
tinymountain at nospam dot gmail dot com
16 年前
這是一個方便的類別,允許使用 flock 重試寫入設定的次數。如果無法 flock,它會休眠一小段隨機時間並重試。如果您有很多並行寫入正在進行,您可以使用它來避免資料損毀。

<?php
class SafeWriter
{
// 建議的模式 'a' 用於寫入檔案末尾
public static function writeData($path, $mode, $data)
{
$fp = fopen($path, $mode);
$retries = 0;
$max_retries = 100;

if (!
$fp) {
// 失敗
return false;
}

// 盡可能嘗試取得鎖定
do {
if (
$retries > 0) {
usleep(rand(1, 10000));
}
$retries += 1;
} while (!
flock($fp, LOCK_EX) and $retries <= $max_retries);

// 無法取得鎖定,放棄
if ($retries == $max_retries) {
// 失敗
return false;
}

// 取得鎖定,寫入資料
fwrite($fp, "$data\n");
// 釋放鎖定
flock($fp, LOCK_UN);
fclose($fp);
// 成功
return true;
}
}
?>
3
rudy dot metzger at pareto dot nl
20 年前
就像一位使用者已經提到的,大多數 Linux 核心(至少 Redhat 的那些)即使您鎖定了檔案,也會傳回 false。這是因為鎖定只是「建議性」的(您可以在 /proc/locks 中檢查)。您必須在那裡做的是評估 flock() 的第三個參數,$eWouldBlock。請參閱下面的範例。但是請注意,如果您
在非封鎖模式下鎖定檔案,flock() 將按預期運作(並封鎖腳本)。

<?php

$fp
= fopen( "/var/lock/process.pid", "a" );
if ( !
$fp || !flock($fp,LOCK_EX|LOCK_NB,$eWouldBlock) || $eWouldBlock ) {
fputs( STDERR, "Failed to acquire lock!\n" );
exit;
}

// 在此處執行您的工作

fclose( $fp );
unlink( "/var/lock/process.pid" );

?>
2
ondrej dot nemecek at gmail dot com
12 年前
請注意:在具有 NFS 鎖定精靈的 NFS 上,您無法開啟檔案進行讀取,然後獨佔鎖定

$rFopened = fopen($sFile,'r');
$bResult = flock($rFopened, LOCK_EX);

var_dump($bResult); // 傳回 FALSE

混合的 fopen 模式 (a+, w+ ...) 使用 LOCK_EX 時效果良好。
1
mallory dot dessaintes at gmail dot com
16 年前
我注意到,如果您變更 fopen 資源的值,鎖定將不再運作。

<?php

$fo
= fopen('lockfile.txt','a');

flock($fo,LOCK_EX);

$fo = '';

// 鎖定已停用

?>
1
marc dot vanwoerkom at fernuni-hagen dot de
18 年前
我陷入一個迴圈,因為我只檢查 true(= 您已取得鎖定)作為 flock() 的傳回值,並且在取得 false 時嘗試重試。

<?php
function naive_wait_for_file($fp) {
while (
true) {
if (
flock($fp, LOCK_EX)) {
return;
}
$k = rand(0, 20);
usleep(round($k * 10000)); # k * 10 毫秒
}
}
?>

不幸的是,在某種情況下,我放入的 $fp 是無效的,所以我總是得到 false 並卡住了。
教訓:在進入迴圈之前檢查您的 $fp 是否有效,或者如果您得到 false,請仔細查看。

<?php
function wait_for_file($fp) {
if (
$fp === false) {
return;
}
while (
true) {
if (
flock($fp, LOCK_EX)) {
return;
}
$k = rand(0, 20);
usleep(round($k * 10000)); # k * 10 毫秒
}
}
?>
1
holdoffhunger at gmail dot com
14 年前
當我讀取檔案、刪除檔案,然後將稍微變更的資訊輸出回相同位置時,我一直難以讓 Flock 運作。使用 Unlink 刪除時,會有一段非常短的時間沒有檔案存在。但是,如果您使用 "w" 模式執行 fopen,它會保持檔案存在,但在您寫入檔案時刪除其所有資料。這樣,檔案實際上永遠不會消失,而另一個使用 flock 存取相同檔案的腳本不會收到「檔案不存在」的錯誤。
1
Kuzma dot Deretuke at gmail dot com
15 年前
我使用獨佔寫入來取代標準的 flock()

<?php
// 取得/設定鎖定檔案名稱
function m_lock_file( $format = null ) {
static
$file_format = './%s.lock';

if (
$format !== null) {
$file_format = $format;
}

return
$file_format;
}

// 取得/檢查/釋放鎖定
function m_lock( $lockId, $acquire = null ) {
static
$handlers = array();

if (
is_bool($acquire)) {
$file = sprintf(m_lock_file(), md5($lockId), $lockId);
}

if (
$acquire === false) {
if (isset(
$handlers[$lockId])) {
@
fclose($handlers[$lockId]);
@
unlink($file);
unset(
$handlers[$lockId]);
} else {
trigger_error("Lock '$lockId' is already unlocked", E_USER_WARNING);
}
}

if (
$acquire === true) {
if (!isset(
$handlers[$lockId])) {
$handler = false;
$count = 100;
do {
if (!
file_exists($file) || @unlink($file)) {
$handler = @fopen($file, "x");
}
if (
false === $handler) {
usleep(10000);
} else {
$handlers[$lockId] = $handler;
}
} while (
false === $handler && $count-- > 0);
} else {
trigger_error("Lock '$lockId' is already locked", E_USER_WARNING);
}
}

return isset(
$handlers[$lockId]);
}
?>

使用範例

<?php
$lockId
= "qq";

m_lock($lockId, true);
if (
m_lock($lockId)) {
echo
"已鎖定";

// 在這裡你可以執行任何執行緒安全的操作
usleep(300 * 1000);

m_lock($lockId, false);
} else {
echo
"未鎖定";
}

?>
1
admin ifyouwantblood de
16 年前
除了手冊上所說的關於鎖定以 w 或 w+ 開啟的檔案,並在這些情況下使用特殊的鎖定檔案之外,你應該在寫入後使用 ftruncate() 自己截斷檔案。

<?php

$data
='一些資料';
$handle=fopen('檔案','r+');
flock($handle,LOCK_EX);
fwrite($handle,$data);
ftruncate($handle,ftell($handle));
flock($handle,LOCK_UN);
fclose($handle);

?>

現在檔案的大小將會是 $data 的大小,而不會以 w 模式開啟檔案,但會鎖定檔案。

給先前的作者 jpriebe 和 mallory
當然,在這種情況下鎖定會遺失,但這僅僅是因為檔案被 PHP 關閉了。關閉檔案意味著解鎖它 (與你自己使用 fclose() 的情況相同)。
1
emilchemical at gmail dot com
8 年前
如果您有興趣使用檔案鎖定作為指示您的主控台應用程式正在執行,並且您不想再次啟動它

if (!flock($fp, LOCK_EX | LOCK_NB, $wouldblock)) {
if ($wouldblock) {
//另一個程序持有鎖定!

} else {
//由於其他原因無法鎖定,例如沒有此檔案

}
} else {
//取得鎖定

startJob();
}
請注意,這是一個平台獨立的解決方案,但是您在 PHP 版本中有限制 (Windows 上的 5.5.22)。
此外,某些舊的檔案系統不支援。
1
damasta at onwebworx dot net
19 年前
只是想說,如果您將單獨的鎖定檔案與 register_shutdown_function 一起使用,您很可能會失敗。

我的腳本執行了一些不同的操作...調整圖片大小、旋轉它們等等。它需要一個「資料庫」檔案來取得正確的檔案位置。此資料庫檔案還儲存了一些旗標。當然,腳本必須在完成時儲存該檔案。

由於我的腳本根據我使用的操作在許多不同的點退出,我使用了 register_shutdown_function 來儲存檔案。它想要使用鎖定系統,以確保腳本不會覆蓋另一個程序在幾微秒前寫入其中的資料。我在我的開發機器上運行 Windows 2000 和 Apache2,由於某些原因,flock 總是返回 true...所以我使用了一個單獨的鎖定檔案。腳本在開始時尋找它,如果找到它就退出。否則它會建立它。但是此檔案必須在最後刪除。我將 unlink 命令放入已註冊的關閉函式中,但它從未刪除該檔案。我嘗試了 clearstatcache 和其他一些東西,但沒有幫助。

也許這對某人有幫助。
3
Evan Battaglia
13 年前
LOCK_NB 似乎在 Windows 中也經過檢查且運作良好,在 PHP 5.3.3 中。

例如,嘗試同時執行以下腳本的兩個實例(透過 CLI)。第二個會如預期輸出「沒有完全取得鎖定...」,而沒有 LOCK_NB 旗標,它只會掛起。

<?php
$x
= fopen("flocktest.txt", "w");
if (
flock($x, LOCK_EX|LOCK_NB)) {
print
"沒問題,我取得了鎖定,現在我要佔用它。";
while (
true)
sleep(5);
} else {
print
"沒有完全取得鎖定。現在退出。晚安。";
}
fclose($x);
?>
1
sinus-php at sinpi dot net
10 年前
「在後續程式碼中將另一個值分配給 handle 引數將會釋放鎖定。」
注意:這對於完全失去 handle 的上下文(例如從 handle 為局部變數的函式返回)也是如此。

<?php
touch
("testfile");

function
mylock() {
$F1 = fopen("testfile","r");
if (
flock($F1,LOCK_EX|LOCK_NB)) echo "第一個鎖定 OK\n"; else echo "第一個鎖定 FAIL\n";
$F2 = fopen("testfile","r");
if (
flock($F2,LOCK_EX|LOCK_NB)) echo "第二個鎖定 OK\n"; else echo "第二個鎖定 FAIL\n";
}

mylock();
echo
"函式已返回。\n";
mylock();

unlink("testfile");
?>

輸出

第一個鎖定 OK
第二個鎖定 FAIL
函式已返回。
第一個鎖定 OK
第二個鎖定 FAIL

這將會鎖定 testfile,然後嘗試使用新的檔案 handle 再次鎖定它(顯然會失敗)。但是,再次嘗試此操作會導致再次正確鎖定(然後再次失敗),因為在退出函式時,兩個 handle 都會遺失並自動解鎖。
1
jerry at gh33 dot org
18 年前
確實,當底層檔案系統是 NFS 時,flock() 無法可靠地運作。在這種情況下,執行檔案鎖定的正確方法是使用 PHP 的 link() 函數。摘自 Linux 的 open() 手冊頁:

O_EXCL 與 O_CREAT 一起使用時,如果檔案已存在,則會發生
錯誤,並且 open 會失敗。在這種情況下,符號連結
存在,無論它指向哪裡。O_EXCL 在
NFS 檔案系統上是損壞的,依賴它來執行鎖定
任務的程式會包含競爭條件。使用鎖定檔案執行
原子檔案鎖定的解決方案是在相同的檔案系統上建立一個
獨特檔案(例如,加入主機名稱和
pid),使用 link(2) 建立到鎖定檔案的連結。如果 link()
返回 0,則鎖定成功。否則,使用 stat(2) 對
該獨特檔案進行檢查,確認其連結計數是否增加到 2,
在這種情況下,鎖定也成功。
2
orkans at gmail dot com
1 年前
注意隱含的鎖定釋放!

<?php
flock
($fp, LOCK_SH);

if(!
flock($fp, LOCK_EX | LOCK_NB)) {
echo
'無法取得獨佔鎖定';
}

// 現在 LOCK_SH 也消失了!

?>
1
andy at andyslife dot org
1 年前
如果使用此方法將類別鎖定為單一實例(長時間執行的程序),則很重要。檔案控制代碼需要像 $this->singleinstance 一樣是類別範圍的變數。而不是像 $localvariable 一樣是 __construct 函數的區域變數。原因是區域變數在 __construct 被呼叫後就會消失,因此鎖定只會持續幾毫秒,而不是類別在記憶體中執行的完整時間。
2
peripeltus at gmail dot com
7 年前
依賴 flock 來同步事物是有風險的,請考慮改用 SYNC 或 pthreads 等擴展。
0
tom dot vom dot berg at online dot de
10 年前
如果您習慣使用 "track_errors = 1" 和 $php_errormsg 來回覆合格的錯誤訊息,如果您使用 LOCK_NB,flock() 將會警告您。

如果 flock() 因 LOCK_NB 而失敗,$php_errormsg 將不會被建立並填入像 '無法鎖定檔案,檔案已鎖定' 這樣的錯誤訊息。

您可以使用一些在 WIN 上也能運作的小技巧

#---------------------------------
GLOBAL $MYERRORMSG;

function some_file_operations($filename)
{
GLOBAL $MYERRORMSG;

# ...
# ...

if (! @flock($fh, LOCK_SH | LOCK_NB, $wb))
{
if (!isset($php_errormsg)) $php_errormsg = '無法鎖定檔案,檔案已鎖定';
$MYERRORMSG = $php_errormsg;
return false;
}
}
#--------------------------------

在失敗的情況下,$MYERRORMSG 現在將會保存一個合格的錯誤訊息
2
candide at idees-et-solutions dot fr
16 年前
關於使用 filemtime() 來鎖定檔案的最後一種方法,只是一個評論。
如果 filemtime($fp[1]) == $fp[3] 是因為有人在 $fp[3] 的值被取得後不到 1 秒就修改了檔案,那會怎樣?
那麼這個修改會遺失嗎...?

這種鎖定檔案的系統旨在防止兩個修改太接近而可能互相干擾的問題,所以「小於 1 秒」的情況會經常發生嗎?

然而,遺失一些修改總比毀壞整個檔案來得好...
-1
daniel AT brightbyte DOT de
15 年前
Solaris 上的 flock 有點奇怪:如果您嘗試在未開啟寫入的檔案上取得獨佔鎖定,它將會失敗。也就是說,對於讀取檔案,您必須使用共享鎖定。摘自 Solaris 的 flock 手冊頁

「需要對檔案有讀取權限才能取得共享鎖定,並且需要寫入權限才能取得獨佔鎖定。」

相反地,這是摘自 Linux 的 flock 手冊頁

「用於開啟檔案的模式對 flock 來說並不重要。」

所以,請注意...
-3
geoff at message dot hu
22 年前
您應該在開啟檔案_之前_鎖定檔案,並在關閉檔案_之後_解鎖。否則,另一個程序可能會在鎖定/解鎖和開啟/關閉操作之間存取該檔案。
-4
mirco dot babin at gmail dot com
14 年前
因為
1) 如果多個 PHP 會話同時鎖定,flock() 是不安全的。
2) 如果多個 PHP 會話同時附加,fopen( , 'a') 是不安全的。
3) usleep 和 sleep 因存在問題而聞名。

我編寫了 Weblog 函數,其目的是將一行附加到記錄中。此函數處理並發的方式如下
- 嘗試建立一個名為:$directory . date('Ymd') . $logfile . 1 . lock 的鎖定檔案
- 如果失敗,嘗試建立一個名為:$directory . date('Ymd') . $logfile . 2 . lock 的鎖定檔案
- 等等。嘗試 100 次後返回 false。

- 當取得鎖定檔案後,開啟或建立名為:$directory.date('Ymd').$logfile.1 (或 .2 或 .3 或 .25)的檔案。
- 如果建立,則會寫入「延伸記錄檔」標頭。
- 寫出該行。
- 關閉檔案,如果建立,則設定正確的存取權限。(我在網頁伺服器上建立檔案時遇到一些問題,當我開啟到網頁目錄的 FTP 會話時,我看不到它們。chmod 對我來說很有效)。

- 移除鎖定檔案。

只有一個缺點,會建立多個記錄檔。
例如(於 2010 年 9 月 15 日執行)
Weblog('./' , 'visit', '有人請求了首頁');
可能會導致 100 個檔案,具體取決於有多少並行的 PHP 會話同時嘗試附加記錄行
./20100915visit.1
./20100915visit.2
./20100915visit.3
./20100915visit.4
...
./20100915visit.100

此函數貢獻給公眾領域。也許您可以留下作者註釋來給我一些功勞,但這不是必需的。您可以隨意修改它。
(此函數的靈感來自 Kuzma dot Deretuke at gmail dot com 提出的函數 m_lock_file)

<?php
function Weblog($directory, $logfile, $message)
{
// Created 15 september 2010: Mirco Babin
$curtime = time();
$logfile = date('Ymd',$curtime) . $logfile;

if (!isset(
$directory) || $directory === false)
$directory = './';
if (
substr($directory,-1) !== '/')
$directory = $directory . '/';

$count = 1;
while(
1)
{
$logfilename = $directory . $logfile . '.' . $count;

$lockfile = $logfilename . '.lock';
$lockhandle = false;
if (!
file_exists($lockfile) || @unlink($lockfile))
$lockhandle = @fopen($lockfile, 'xb');
if (
$lockhandle !== false) break;

$count++;
if (
$count > 100) return false;
}

if (
file_exists($logfilename))
{
$created = false;
$loghandle = @fopen($logfilename, 'ab');
}
else
{
$loghandle = @fopen($logfilename, 'xb');
if (
$loghandle !== false)
{
$created = true;

$str = '#version: 1.0' . "\r\n" .
'#Fields: date time c-ip x-msg' . "\r\n";
fwrite($loghandle,$str);
}
}

if (
$loghandle !== false)
{
$str = date('Y-m-d',$curtime) . "\t" .
date('H:i:s', $curtime) . "\t" .
(isset(
$_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-') . "\t" .
'"' . str_replace('"', '""', $message) . '"' . "\r\n";
fwrite($loghandle,$str);

fclose($loghandle);

if (
$created) chmod($logfilename,0644); // Read and write for owner, read for everybody else

$result = true;
}
else
{
$result = false;
}

fclose($lockhandle);
@
unlink($lockfile);

return
$result;
}
?>
-3
steven at aubergine-it dot nl
11 年前
您無法將 gzopen 與 flock 結合使用;所以

<?php
$f
= gzopen($file,'w');
$o = flock($f, LOCK_EX);
var_dump($o);
flush();
?>

會產生

布林值 false
-3
Will Reiher
17 年前
我一直在測試一些自訂的檔案存取函式,但我一直很喜歡 file_get_contents() 的簡潔性。當然,它似乎不尊重任何使用 flock() 建立的檔案鎖定。我建立以下函式來包裝 file_get_contents(),使其可以支援鎖定的檔案。這是一種奇怪的處理方式,但對我來說很有效。

<?php
function flock_get_contents($filename){

$return = FALSE;

if(
is_string($filename) && !empty($filename)){
if(
is_readable($filename)){
if(
$handle = @fopen($filename, 'r')){
while(!
$return){
if(
flock($handle, LOCK_SH)){
if(
$return = file_get_contents($filename)){
flock($handle, LOCK_UN);
}
}
}
fclose($handle);
}
}
}

return
$return;
}
?>
-3
enyby at yandex dot ru
10 年前
在已處理的檔案上使用 rename 或 unlink 會中斷在處理上的 LOCK_EX flock(僅限 Linux)。

範例

$handle = fopen($lockFile, 'c');
var_dump(flock($handle, LOCK_EX | LOCK_NB)); // false,因為 flock 使用其他 php 程序
rename($lockFile, $lockFile.'.old'); // 或 unlink
var_dump(flock($handle, LOCK_EX | LOCK_NB)); // 總是 true (!!!)
-4
Glip
20 年前
如果您不想在截斷時使用輔助鎖定檔案,請嘗試這個

<?php

$fp
= fopen("/tmp/lock.txt", "a+");

if (
flock($fp, LOCK_EX)) { // 執行獨佔鎖定
ftruncate($fp, 0);
fwrite($fp, "Write something here\n");
flock($fp, LOCK_UN); // 釋放鎖定
} else {
echo
"Couldn't lock the file !";
}

fclose($fp);

?>
To Top