今天花了近乎一天的時間研究python關於多線程的問題,查看了大量源碼 本身也實踐了一個生產消費者模型,因此把一天的收穫總結一下。python
因爲GIL(Global Interpreter Lock)鎖的關係,純的python代碼處理通常邏輯的確沒法活動性能上的極大提高,可是在處理須要等待外部資源返回或多用戶的應用程序中,多線程仍然能夠做爲一個比較好的工具來進行使用。編程
python提供了兩個模塊thread和threading 來支持python的多線程操做。通俗的講通常如今咱們只使用threading模塊來編程了,thread模塊定義了不少原始行爲,更接近底層,而threading模塊抽象了thread模塊可用性更好,同時提供更多特性。多線程
如今建立線程的通用方法通常是建立一個類而且繼承threading.Thread,而後重寫其中的__init__和run()方法。 更多詳情能夠參考threading模塊代碼內註釋以及代碼。下面直接看個例子。併發
import time import threading class Test(threading.Thread): def __init__(self, name, delay): super(Test, self).__init__() self.name = name self.delay = delay def run(self): print "%s delay for %s seconds" % (self.name, self.delay) time.sleep(self.delay) c = 0 while True: print "This is thread %s on line %s" % (self.name, c) c += 1 if c == 3: print "End of thread %s" % self.name break t1 = Test('Thread1', 5) t2 = Test('Thread2', 5) t1.start() print 'Wait t1 to end' t1.join() t2.start() t2.join() print 'End of main'
注意一下這一句 :dom
super(Test, self).__init__()
這是按照模塊要求,必須初始化父類的__init__函數 因此使用了super()函數
其餘並無多少值得注意的地方,工具
建立線程方便的實例化本身寫的繼承threading.Thread的類 而後傳入對應的參數。性能
最後使用xxx.start()來運行線程。 使用xxx.join()來阻塞線程。ui
特別注意的是。繼承自Threading類的子類還有一個daemon參數,若是這個參數適用setDaemon()方法置爲True以後,主線程將不會等待子線程都結束以後才結束,而是本身運行完以後就結束,這種方式至關粗暴。 若是將daemon參數設置爲False的話,主線成將會等待全部子線程結束以後再結束。daemon屬性能夠經過使用isDaemon()方法獲取一個boolean值。操作系統
更進一步的,我必須介紹一下線程之間的同步和互斥問題。下面引用《計算機操做系統》中的介紹。
當線程併發執行時,因爲資源共享和線程協做,使用線程之間會存在如下兩種制約關係。
(1)間接相互制約。一個系統中的多個線程必然要共享某種系統資源,如共享CPU,共享I/O設備,所謂間接相互制約即源於這種資源共享,打印機就是最好的例子,線程A在使用打印機時,其它線程都要等待。
(2)直接相互制約。這種制約主要是由於線程之間的合做,若有線程A將計算結果提供給線程B做進一步處理,那麼線程B在線程A將數據送達以前都將處於阻塞狀態。
間接相互制約能夠稱爲互斥,直接相互制約能夠稱爲同步,對於互斥能夠這樣理解,線程A和線程B互斥訪問某個資源則它們之間就會產個順序問題——要麼線程A等待線程B操做完畢,要麼線程B等待線程操做完畢,這其實就是線程的同步了。所以同步包括互斥,互斥實際上是一種特殊的同步。
在一段時間內只容許一個線程訪問的資源就稱爲臨界資源或獨佔資源,計算機中大多數物理設備,進程中的共享變量等待都是臨界資源,它們要求被互斥的訪問。每一個進程中訪問臨界資源的代碼稱爲臨界區。
這裏爲了介紹這種稍微複雜的概念。 再列出一個生產消費者的例子 使用到了Queue隊列。
# coding:utf-8 import Queue import time import random import threading # write_lock = threading.Lock() # 建立primitive鎖對象用於控制輸出 class Producer(threading.Thread): # q傳遞一個隊列參數, con傳遞了一個連接, name傳遞了一個名字 def __init__(self, q, name): super(Producer, self).__init__() self.q = q # self.con = con self.name = name print "Producer " + self.name + "Started" def run(self): while True: # 鎖對象經常使用的acquire得到鎖方法和release釋放鎖方法 # 這裏使用的是Thread的Condition對象 # self.con.acquire() if self.q.full(): print 'Queue is full, producer wait!' # 手動掛起,而且只能在得到Lock的狀況下才可使用 不然會觸發RuntimeError # 調用wait()會釋放Lock 直到該線程被notify(),notifyall()或超時該線程又從新得到Lock # self.con.wait() else: value = random.randint(0, 10) print self.name + " put " + str(value) + "into queue" self.q.put((self.name+":"+str(value))) # 放置到隊列中 # 通知消費者,notify通知其餘線程,被掛起的線程接到通知後會開始運行 # 默認通知一個正在等待該condition的線程,最多喚醒n個線程 必須在得到Lock的狀況下使用不然會報錯. # self.con.notify() # self.con.release() # 釋放鎖對象 class Consumer(threading.Thread): def __init__(self, q, name): super(Consumer, self).__init__() self.q = q # self.con = con self.name = name print "Consumer " + self.name + "started\n" def run(self): while True: # Condition經常使用的acquire得到鎖方法和release釋放鎖方法 # self.con.acquire() if self.q.empty(): print 'queue is empty, consumer wait!' # self.con.wait() else: value = self.q.get() # 從隊列中取消息 print self.name + " get " + value + "from queue" # 發送消息通知生產者 # self.con.notify() # self.con.release() # 釋放鎖對象 print 'queue still have ' + str(q.qsize()) + 'task\n' if __name__ == "__main__": q = Queue.Queue(10) # 使用Condition對象能夠在某些事件觸發或達到特定的條件後才處理數據. # con = threading.Condition() # 兩個生產者 p1 = Producer(q, "P1") p2 = Producer(q, "P2") c1 = Consumer(q, "C1") p2.start() p1.start() c1.start()
如有問題歡迎指出
python threading模塊文檔翻譯: http://my.oschina.net/lionets/blog/194577?fromerr=pbWOeveo
多線程7經典線程與互斥總結:http://blog.csdn.net/dazhong159/article/details/7927034
《編寫高質量代碼改善python程序的91個建議》第48和49建議。