一、Lock的作用及使用方法
在多線程中,為了防止數據錯亂或者變數衝突,需要對線程進行同步。Python提供了多種同步方式,其中Lock是最基本的同步方法之一。
Lock對象可以看作是一個標誌,表示只有當獲取該標誌的線程釋放該標誌之後,其他線程才能獲取它。這樣就可以保證同一時間只有一個線程訪問共享資源。
使用Lock對象時,需要獲取該對象的鎖定,並在需要訪問共享資源的代碼塊前調用acquire()方法,使用完後要利用release()方法釋放鎖定。
import threading import time class MyThread(threading.Thread): def __init__(self, thread_num, lock): threading.Thread.__init__(self) self.thread_num = thread_num self.lock = lock def run(self): print("Thread {} start".format(self.thread_num)) self.lock.acquire() print("Thread {} acquired lock".format(self.thread_num)) time.sleep(1) self.lock.release() print("Thread {} released lock".format(self.thread_num)) if __name__ == '__main__': lock = threading.Lock() threads = [] for i in range(5): threads.append(MyThread(i, lock)) for t in threads: t.start() for t in threads: t.join()
以上代碼展示了Lock對象的使用方法。在這個例子中,創建了5個線程MyThread,每個線程都需要獲取同一個Lock對象的鎖定,在獲取鎖定後暫停1秒鐘,然後釋放鎖定。
二、Lock的帶計數器版本RLock
在Python中,還有一種類似Lock的帶計數器版本,叫做RLock(R=Reentrant, 可重入)。相對於Lock而言,RLock多了一個計數器。同一個線程在多次調用acquire()方法後,只有在計數器為0的情況下,其他線程才能獲取該鎖。
可以通過調用Lock對象的方法獲得RLock對象,就像下面這樣:
import threading import time class MyThread(threading.Thread): def __init__(self, thread_num, lock): threading.Thread.__init__(self) self.thread_num = thread_num self.lock = lock def run(self): print("Thread {} start".format(self.thread_num)) self.lock.acquire() print("Thread {} acquired lock".format(self.thread_num)) time.sleep(1) self.lock.acquire() print("Thread {} acquired lock again".format(self.thread_num)) time.sleep(1) self.lock.release() print("Thread {} released lock".format(self.thread_num)) time.sleep(1) self.lock.release() print("Thread {} released lock again".format(self.thread_num)) if __name__ == '__main__': lock = threading.RLock() threads = [] for i in range(5): threads.append(MyThread(i, lock)) for t in threads: t.start() for t in threads: t.join()
在上述的代碼中,MyThread線程在第一次獲取RLock對象鎖定後,調用了acquire()方法增加了計數器的值,然後又調用了一次acquire()方法,再次獲取該鎖定,計數器的值增加再次。通過兩次計數器值的增加,就達到了可重入的目的。
三、使用Lock對象避免死鎖
Lock對象雖然可以很好保證同步,但是如果在使用不當的情況下,可能會出現死鎖的問題。比如,線程A獲取了鎖1,想要獲取鎖2;線程B獲取了鎖2,想要獲取鎖1,這時就出現了死鎖。
避免死鎖的方法是,在獲取所有的鎖前,先獲取其中一個,如果沒有獲得,則釋放已經獲得的鎖,等待一段時間後重試。這段等待的時間,可以通過調整參數來優化。
下面是一個典型的死鎖示例:
import threading def worker1(lock1, lock2): lock1.acquire() print("Worker1 acquired lock1") lock2.acquire() print("Worker1 acquired lock2") lock1.release() print("Worker1 released lock1") lock2.release() print("Worker1 released lock2") def worker2(lock1, lock2): lock2.acquire() print("Worker2 acquired lock2") lock1.acquire() print("Worker2 acquired lock1") lock2.release() print("Worker2 released lock2") lock1.release() print("Worker2 released lock1") if __name__ == '__main__': lock1 = threading.Lock() lock2 = threading.Lock() t1 = threading.Thread(target=worker1, args=(lock1, lock2)) t2 = threading.Thread(target=worker2, args=(lock1, lock2)) t1.start() t2.start() t1.join() t2.join()
以上代碼演示了死鎖。在該示例中,worker1和worker2都需要獲取兩個Lock對象的鎖定,如果兩個線程在獲取鎖定的順序不同,則可能會出現死鎖。在這種情況下,就需要按照特定的順序獲取鎖定,避免死鎖。比如下面的程序:
import threading import time def worker1(lock1, lock2): print("Worker1 waiting for lock1") lock1.acquire() print("Worker1 acquired lock1") time.sleep(1) while not lock2.acquire(blocking=False): print("Worker1 waiting for lock2") time.sleep(1) print("Worker1 acquired lock2") lock1.release() print("Worker1 released lock1") lock2.release() print("Worker1 released lock2") def worker2(lock1, lock2): print("Worker2 waiting for lock1") lock2.acquire() print("Worker2 acquired lock2") time.sleep(1) while not lock1.acquire(blocking=False): print("Worker2 waiting for lock1") time.sleep(1) print("Worker2 acquired lock1") lock2.release() print("Worker2 released lock2") lock1.release() print("Worker2 released lock1") if __name__ == '__main__': lock1 = threading.Lock() lock2 = threading.Lock() t1 = threading.Thread(target=worker1, args=(lock1, lock2)) t2 = threading.Thread(target=worker2, args=(lock1, lock2)) t1.start() t2.start() t1.join() t2.join()
在上述的程序中,不管worker1和worker2的獲取鎖的順序如何,先獲取的線程會最終釋放自己獲取的鎖,再次獲取另一個鎖。同時如果不成功獲取另一個鎖,則會等待一段時間後重試獲取鎖定,這樣就可以避免死鎖。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/279493.html