為了確保操作的有效性和完整性,可以通過鎖機制將并發狀態轉換成串行狀態.作為鎖機制中的一種,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
(2) b.php
運行 a.php 后,馬上運行 b.php ,可以看到輸出:
abc
等 a.php 運行完后運行 b.php ,可以看到輸出:
abc
123
顯然,當 a.php 寫文件時數據太大,導致時間比較長時,這時 b.php 讀取數據不完整
修改 b.php 為:
實例代碼如下:
運行 a.php 后,馬上運行 b.php ,可以發現 b.php 會等到 a.php 運行完成后(即 10 秒后)才顯示:
讀取數據完整,但時間過長,他要等待寫鎖釋放.修改 b.php 為:
實例代碼如下:
運行 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寫完并釋放鎖之后才進行讀.