萬盛學電腦網

 萬盛學電腦網 >> 網絡編程 >> php編程 >> PHP flock文件鎖詳解介紹

PHP flock文件鎖詳解介紹

為了確保操作的有效性和完整性,可以通過鎖機制將並發狀態轉換成串行狀態。作為鎖機制中的一種,PHP的文件鎖也是為了應對資源競爭。假設一個應用場景,在存在較大並發的情況下,通過fwrite向文件尾部多次有序的寫入數據,不加鎖的情況下會發生什麼?多次有序的寫入操作相當於一個事務,我們此時需要保證這個事務的完整性。

bool flock ( int handle, int operation [, int &wouldblock] );
flock() 操作的 handle 必須是一個已經打開的文件指針。operation 可以是以下值之一:

1.要取得共享鎖定(讀取程序),將 operation 設為 LOCK_SH(PHP 4.0.1 以前的版本設置為 1)
2.要取得獨占鎖定(寫入程序),將 operation 設為 LOCK_EX(PHP 4.0.1 以前的版本中設置為 2)
3.要釋放鎖定(無論共享或獨占),將 operation 設為 LOCK_UN(PHP 4.0.1 以前的版本中設置為 3)
4.如果你不希望 flock() 在鎖定時堵塞,則給 operation 加上 LOCK_NB(PHP 4.0.1 以前的版本中設置為 4)

建兩個文件

 代碼如下 復制代碼

(1) a.php

?$file = "temp.txt";    
$fp = fopen($file , 'w');    
if(flock($fp , LOCK_EX)){    
     fwrite($fp , "abcn");    
     sleep(10);    
     fwrite($fp , "123n");    
    flock($fp , LOCK_UN);    
}    
fclose($fp);

(2) b.php

?$file = "temp.txt";    
$fp = fopen($file , 'r');    
echo fread($fp , 100);    
fclose($fp);

運行 a.php 後,馬上運行 b.php ,可以看到輸出:
abc
等 a.php 運行完後運行 b.php ,可以看到輸出:
abc
123
顯然,當 a.php 寫文件時數據太大,導致時間比較長時,這時 b.php 讀取數據不完整

修改 b.php 為:

 代碼如下 復制代碼 ?$file = "temp.txt";    
$fp = fopen($file , 'r');    
if(flock($fp , LOCK_EX)){    
    echo fread($fp , 100);    
    flock($fp , LOCK_UN);    
} else{    
    echo "Lock file failed...n";    
}    
fclose($fp);

運行 a.php 後,馬上運行 b.php ,可以發現 b.php 會等到 a.php 運行完成後(即 10 秒後)才顯示:
abc
123
讀取數據完整,但時間過長,他要等待寫鎖釋放。

修改 b.php 為:

 代碼如下 復制代碼 ?$file = "temp.txt";    
$fp = fopen($file , 'r');    
if(flock($fp , LOCK_SH | LOCK_NB)){    
    echo fread($fp , 100);    
    flock($fp , LOCK_UN);    
} else{    
    echo "Lock file failed...n";    
}    
fclose($fp);

運行 a.php 後,馬上運行 b.php ,可以看到輸出:
Lock file failed…
證明可以返回鎖文件失敗狀態,而不是向上面一樣要等很久。

結論:

建議作文件緩存時,選好相關的鎖,不然可能導致讀取數據不完整,或重復寫入數據。
file_get_contents 好像選擇不了鎖,不知道他默認用的什麼鎖,反正和不鎖得到的輸出一樣,是不完整的數據。
我是要做文件緩存,所以只需要知道是否有寫鎖存在即可,有的話就查數據庫就可以了。


多次同時執行,雖然都寫了100行,但是事務1和事務2的數據交錯寫入,這並不是我們想要的結果。我們要的是事務完整的執行,此時我們需要有個機制去保證在第一個事務執行完後再執行第二個。在PHP中,flock函數完成了這一使命。在事物1和事務2的循環前面都加上: flock($fp, LOCK_EX); 就能滿足我們的需求,將兩個事務串行。

當某一個事務執行完flock時,因為我們在這裡添加的是LOCK_EX(獨占鎖定),所以所有對資源的操作都會被阻塞,只有當事務執行完成後,後面的事務才會執行。我們可以通過輸出當前的時間的方法來確認這一點。

關於在尾部追加寫入,在unix系統的早期版本中存在一個並發寫入的問題,如果要在尾部追加,需要先lseek位置,再write。當多個進程同時操作時,會因為並發導致的覆蓋寫入的問題,即兩個進程同時獲取尾部的偏移後,先後執行write操作,後面的操作會將前面的操作覆蓋。這個問題在後面以添加打開時的O_APPEND操作而得到解決,它將查找和寫入操作變成了一個原子操作。

在PHP的fopen函數的實現中,如果我們使用a參數在文件的尾部追加內容,其調用open函數中oflag參數為 O_CREAT|O_APPEND,即我們使用追加操作不用擔心並發追加寫入的問題。

在PHP的session默認存儲實現中也用到了flock文件鎖,當session開始時就調用PS_READ_FUNC,且以O_CREAT | O_RDWR | O_BINARY 打開session數據文件,此時會調用flock加上寫鎖,如果此時有其它進程訪問此文件(即同一用戶再次發起對當前文件的請求),就會顯示頁面加載中,進程被阻塞了。加寫鎖其出發點是為了保證此次會話中對session的操作事務能完整的執行,防止其它進程的干擾,保證數據的一致性。如果一個頁面沒有session修改操作,可以盡早的調用session_write_close()釋放鎖。

文件鎖是針對文件的鎖,除了這種釋義,還可以理解為用文件作為鎖。在實際工作中,有時為確保單個進程的執行,我們會在程序執行前判斷文件是否存在,如果不存在則創建一個空文件,在進程結束後刪除這個空文件,如果存在,則不執行。

但是什麼時候使用lock_ex什麼時候使用lock_sh呢?

讀的時候:

如果不想出現dirty數據,那麼最好使用lock_sh共享鎖。可以考慮以下三種情況:
1. 如果讀的時候沒有加共享鎖,那麼其他程序要寫的話(不管這個寫是加鎖還是不加鎖)都會立即寫成功。如果正好讀了一半,然後被其他程序給寫了,那麼讀的後一半就有可能跟前一半對不上(前一半是修改前的,後一半是修改後的)
2. 如果讀的時候加上了共享鎖(因為只是讀,沒有必要使用排他鎖),這個時候,其他程序開始寫,這個寫程序沒有使用鎖,那麼寫程序會直接修改這個文件,也會導致前面一樣的問題
3. 最理想的情況是,讀的時候加鎖(lock_sh),寫的時候也進行加鎖(lock_ex),這樣寫程序會等著讀程序完成之後才進行操作,而不會出現貿然操作的情況

寫的時候:
 
如果多個寫程序不加鎖同時對文件進行操作,那麼最後的數據有可能一部分是a程序寫的,一部分是b程序寫的
如果寫的時候加鎖了,這個時候有其他的程序來讀,那麼他會讀到什麼東西呢?
1. 如果讀程序沒有申請共享鎖,那麼他會讀到dirty的數據。比如寫程序要寫a,b,c三部分,寫完a,這時候讀讀到的是a,繼續寫b,這時候讀讀到的是ab,然後寫c,這時候讀到的是abc.
2. 如果讀程序在之前申請了共享鎖,那麼讀程序會等寫程序將abc寫完並釋放鎖之後才進行讀。 

copyright © 萬盛學電腦網 all rights reserved