一、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
微信掃一掃
支付寶掃一掃