Python中多線程使用到Threading模組。Threading模組中用到的主要的類是Thread,我們先來寫一個簡單的多線程代碼:
複製代碼 代碼如下:
# coding : uft-8
__author__ = 'Phtih0n'
import threading
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global n
print n
n += 1
if "__main__" == __name__:
n = 0
ThreadList = []
for i in range(0, 10):
t = MyThread()
ThreadList.append(t)
for t in ThreadList:
t.start()
for t in ThreadList:
t.join
最普通的一個多線程小例子。我一筆帶過地講一講,我建立了一個繼承Thread類的子類MyThread,作為我們的線程啟動類。按照規定,重寫Thread的run方法,我們的線程啟動起來後會自動調用該方法。於是我首先建立了10個線程,並將其加入列表中。再使用一個for迴圈,開啟每個線程。在使用一個for迴圈,調用join方法等待所有線程結束才退出主線程。
這段代碼看似簡單,但實際上隱藏著一個很大的問題,只是在這裡沒有體現出來。你真的以為我建立了10個線程,並按順序調用了這10個線程,每個線程為n增加了1.實際上,有可能是A線程執行了n++,再C線程執行了n++,再B線程執行n++。
這裡涉及到一個“鎖”的問題,如果有多個線程同時操作一個對象,如果沒有很好地保護該對象,會造成程式結果的不可預期(比如我們在每個線程的run方法中加入一個time.sleep(1),並同時輸出線程名稱,則我們會發現,輸出會亂七八糟。因為可能我們的一個print語句只列印出一半的字元,這個線程就被暫停,執行另一個去了,所以我們看到的結果很亂),這種現象叫做“線程不安全”:
於是,Threading模組為我們提供了一個類,Threading.Lock,鎖。我們建立一個該類對象,線上程函數執行前,“搶佔”該鎖,執行完成後,“釋放”該鎖,則我們確保了每次只有一個線程佔有該鎖。這時候對一個公用的對象進行操作,則不會發生線程不安全的現象了。
於是,我們把代碼更改如下:
複製代碼 代碼如下:
# coding : uft-8
__author__ = 'Phtih0n'
import threading, time
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global n, lock
time.sleep(1)
if lock.acquire():
print n , self.name
n += 1
lock.release()
if "__main__" == __name__:
n = 1
ThreadList = []
lock = threading.Lock()
for i in range(1, 200):
t = MyThread()
ThreadList.append(t)
for t in ThreadList:
t.start()
for t in ThreadList:
t.join()
最後執行結果:
我們看到,我們先建立了一個threading.Lock類對象lock,在run方法裡,我們使用lock.acquire()獲得了這個鎖。此時,其他的線程就無法再獲得該鎖了,他們就會阻塞在“if lock.acquire()”這裡,直到鎖被另一個線程釋放:lock.release()。
所以,if語句中的內容就是一塊完整的代碼,不會再存在執行了一半就暫停去執行別的線程的情況。所以最後結果是整齊的。
就如同在java中,我們使用synchronized關鍵字修飾一個方法,目的一樣,讓某段代碼被一個線程執行時,不會打斷跳到另一個線程中。
這是多線程佔用一個公用對象時候的情況。如果多個線程要調用多個現象,而A線程調用A鎖佔用了A對象,B線程調用了B鎖佔用了B對象,A線程不能調用B對象,B線程不能調用A對象,於是一直等待。這就造成了線程“死結”。
Threading模組中,也有一個類,RLock,稱之為可重新進入鎖。該鎖對象內部維護著一個Lock和一個counter對象。counter對象記錄了acquire的次數,使得資源可以被多次require。最後,當所有RLock被release後,其他線程才能擷取資源。在同一個線程中,RLock.acquire可以被多次調用,利用該特性,可以解決部分死結問題。
死結問題很複雜,多年來人們想出了很多演算法來解決它。我就不再多說,具體還是要大家參閱協助文檔。