1. 程式人生 > >Python多執行緒讀寫檔案加鎖

Python多執行緒讀寫檔案加鎖

Python的多執行緒在io方面比單執行緒還是有優勢,但是在多執行緒開發時,少不了對檔案的讀寫操作。在管理多個執行緒對同一檔案的讀寫操作時,就少不了檔案鎖了。

使用fcntl

在linux下,python的標準庫有現成的檔案鎖,來自於fcntl模組。這個模組提供了unix系統fcntl()和ioctl()的介面。

對於檔案鎖的操作,主要需要使用 fcntl.flock(fd, operation)這個函式。

其中,引數 fd 表示檔案描述符;引數 operation 指定要進行的鎖操作,該引數的取值有如下幾種:

  • LOCK_SH:表示要建立一個共享鎖,在任意時間內,一個檔案的共享鎖可以被多個程序擁有
  • LOCK_EX:表示建立一個排他鎖,在任意時間內,一個檔案的排他鎖只能被一個程序擁有
  • LOCK_UN:表示刪除該程序建立的鎖
  • LOCK_MAND:它主要是用於共享模式強制鎖,它可以與 LOCK_READ 或者 LOCK_WRITE聯合起來使用,從而表示是否允許併發的讀操作或者併發的寫操作

demo

import fcntl
import threading
import time


def writetoTxt(txtFile):
    id = threading.currentThread().getName()
    with open(txtFile, 'a') as f:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX) #加鎖
        print "{0} acquire lock".format(id)
        f.write("write from {0} \r\n".format(id))
        time.sleep(3)
    # 在with塊外,檔案關閉,自動解鎖
    print "{0} exit".format(id)


for i in range(5):
    myThread = threading.Thread(target=writetoTxt, args=("test.txt",))
    myThread.start()

程式碼執行期間,控制檯將依次列印哪個執行緒獲得了鎖,在對檔案進行讀寫。

Thread-1 acquire lock
Thread-1 exit
Thread-2 acquire lock
Thread-2 exit
Thread-3 acquire lock
Thread-3 exit
Thread-5 acquire lock
Thread-5 exit
Thread-4 acquire lock
Thread-4 exit

小結

通過呼叫

fcntl.flock(f.fileno(), fcntl.LOCK_EX)

對檔案加鎖,如果有其他執行緒嘗試對test檔案加鎖,會被阻塞。

當執行緒執行完畢的時候,鎖會自動釋放。或者也可以採取主動的方式解鎖:呼叫

fcntl.flock(f.fileno(),fcntl.LOCK_UN)

函式, 對檔案test解鎖

使用執行緒鎖

當多個執行緒共享一個數據的時候,必須要進行同步的控制,不然會出現不可預期的結果,即 “執行緒不安全”

執行緒同步能夠保證多個執行緒安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。
互斥鎖為資源引入一個狀態:鎖定/非鎖定。
某個執行緒要更改共享資料時,先將其鎖定,此時資源的狀態為“鎖定”,其他執行緒不能更改;
直到該執行緒釋放資源,將資源的狀態變成“非鎖定”,其他的執行緒才能再次鎖定該資源。
互斥鎖保證了每次只有一個執行緒進行寫入操作,從而保證了多執行緒情況下資料的正確性。

threading模組中定義了Lock類,可以方便的處理鎖定:

#建立鎖
mutex = threading.Lock()
#鎖定
mutex.acquire([timeout])
#解鎖
mutex.release()

Demo

使用互斥鎖實現上面的例子的程式碼如下:

import threading
import time

def writetoTxt(txtFile):
    id = threading.currentThread().getName()
    mutex.acquire(10)
    with open(txtFile, 'a') as f:
        print "Thread {0} acquire lock".format(id)
        f.write("write from thread {0} \r\n".format(id))
        time.sleep(3)
    mutex.release()
    print "Thread {0} exit".format(id)


mutex = threading.Lock()

for i in range(5):
    myThread = threading.Thread(target=writetoTxt, args=("test.txt",))
    myThread.start()

(上述程式碼本質上是一個順序執行的單執行緒)

結果:

Thread Thread-1 acquire lock
Thread Thread-1 exit
Thread Thread-2 acquire lock
Thread Thread-2 exit
Thread Thread-3 acquire lock
Thread Thread-3 exit
Thread Thread-4 acquire lock
Thread Thread-4 exit
Thread Thread-5 acquire lock
Thread Thread-5 exit

小結

當一個執行緒呼叫鎖的acquire()方法獲得鎖時,鎖就進入“locked”狀態。每次只有一個執行緒可以獲得鎖。如果此時另一個執行緒試圖獲得這個鎖,該執行緒就會變為“blocked”狀態,稱為“同步阻塞”。
直到擁有鎖的執行緒呼叫鎖的release()方法釋放鎖之後,鎖進入“unlocked”狀態。執行緒排程程式從處於同步阻塞狀態的執行緒中選擇一個來獲得鎖,並使得該執行緒進入執行(running)狀態。