Python中的多線程是共享所在程序的資源和記憶體位址的,是以當多個線程同時操作同一個資料的時候,就容易出錯。如下
from threading import Thread,currentThread
import time
def task():
global n
tem = n
time.sleep(0.01) # 在此處休息0.01秒,足夠開啟所有的線程,他們所擷取到的n都是0,這就導緻了最後輸出的n為1
tem = tem + 1
n = tem
print("%s :n = %d" % (currentThread().name, n))
if __name__ == '__main__':
n = 0
th_li = []
for i in range(10):
th = Thread(target=task)
th.start()
th_li.append(th)
for i in th_li:
i.join() # 等待所有線程執行完畢
print(n)
最後輸出n等于1。為了避免這種情況的發生,我們可以在程序start()後馬上join()這樣就能夠将并發改為串行。還有一種方式就是加鎖,和多程序中的互斥鎖一樣。
from threading import Thread,currentThread,Lock
import time
def task():
metux.acquire()
global n
tem = n
time.sleep(0.01)
tem = tem + 1
n = tem
print("%s :n = %d" % (currentThread().name, n))
metux.release()
if __name__ == '__main__':
n = 0
th_li = []
metux = Lock()
for i in range(10):
th = Thread(target=task)
th.start()
th_li.append(th)
for i in th_li:
i.join()
print(n)
我們可以通過加鎖和解鎖來将并發改為串行,保證了資料的安全。但如果我們加鎖之後,忘記了解鎖,這樣程式就會卡死。為此Python提供了一種便捷的寫法
def task():
metux.acquire()
global n
tem = n
time.sleep(0.01)
tem = tem + 1
n = tem
print("%s :n = %d" % (currentThread().name, n))
metux.release()
上面這段代碼可以簡寫為
def task():
with metux:
global n
tem = n
time.sleep(0.01)
tem = tem + 1
n = tem
print("%s :n = %d" % (currentThread().name, n))
這樣就不用擔心加鎖和解鎖的問題。加鎖是為了将任務的某個部分由并發改為串行,進而保證資料的安全。我們有時候也需要給任務加多把鎖。但多把鎖也容易産生新的問題:死鎖
from threading import Thread,Lock
import time
class MyThread(Thread):
def th1(self):
l1.acquire()
print(self.name," 拿到了 LOKC-1")
l2.acquire()
print(self.name, " 拿到了 LOKC-2")
l2.release()
l1.release()
def th2(self):
l2.acquire()
print(self.name," 拿到了 LOKC-2")
time.sleep(1)
l1.acquire()
print(self.name, " 拿到了 LOKC-1")
l1.release()
l2.release()
def run(self):
self.th1()
self.th2()
if __name__ == "__main__":
l1 = Lock()
l2 = Lock()
for i in range(10):
mt = MyThread()
mt.start()
執行結果
Thread-1 拿到了 LOKC-1
Thread-1 拿到了 LOKC-2
Thread-1 拿到了 LOKC-2
Thread-2 拿到了 LOKC-1
這裡Thread-1拿到了 LOKC-2,準備拿LOKC-1。這裡Thread-2拿到了 LOKC-1,準備拿LOKC-2。Thread-1需要的LOKC-1在Thread-2手中還沒有釋放掉,Thread-2需要的LOKC-2在Thread-1手中還沒有釋放掉。2個線程都在等待對方釋放自己所需要的鎖。程式就在此處卡死了。這就是死鎖現象。為了解決這個問題,Python中的遞歸鎖(RLock)就産生。
RLock内部包含着一個Lock和一個counter變量,counter記錄了acquire的次數,進而使得資源可以被多次acquire。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。上面的例子如果使用RLock代替Lock,則不會發生死鎖: