咱們在作軟件開發的時候不少要用到多線程技術。例如若是作一個下載軟件象flashget就要用到、象在線視頻工具realplayer也要用到由於要同時下載media stream還要播放。其實例子是不少的。python

線程相對進程來講是「輕量級」的,操做系統用較少的資源建立和管理線程。程序中的線程在相同的內存空間中執行,並共享許多相同的資源。編程

python中如何建立一個線程對象多線程

若是你要建立一個線程對象,很簡單,只要你的類繼承threading.Thread,而後在__init__裏首先調用threading.Thread__init__方法便可dom

import threading
class mythread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name = threadname)
....
異步

這才僅僅是個空線程,我可不是要他拉空車的,他可得給我乾點實在活。很簡單,重寫類的run()方法便可,把你要在線程執行時作的事情都放到裏面函數

import threading
import time
class mythread(threading.Thread):
def __init__(...):
....
def run(self):
for i in range(10):
print self.getName, i
time.sleep(1)
工具

以上代碼咱們讓這個線程在執行以後每隔1秒輸出一次信息到屏幕,10次後結束post

getName()threading.Thread類的一個方法,用來得到這個線程對象的name。還有一個方法setName()固然就是來設置這個線程對象的name的了。ui

若是要建立一個線程,首先就要先建立一個線程對象spa

mythread1 = mythread('mythread 1')

一個線程對象被建立後,他就處於「born」(誕生狀態)

如何讓這個線程對象開始運行呢?只要調用線程對象的start()方法便可

mythread1.start()

如今線程就處於「ready」狀態或者也稱爲「runnable」狀態。

奇怪嗎?不是已經start了嗎?爲何不稱爲「running」狀態呢?實際上是有緣由的。由於咱們的計算機通常是不具備真正並行處理能力的。咱們所謂的多線程只是把時間分紅片斷,而後隔一個時間段就讓一個線程執行一下,而後進入「sleeping 」狀態,而後喚醒另外一個在「sleeping」的線程,如此循環runnable->sleeping->runnable... ,只是由於計算機執行速度很快,而時間片斷間隔很小,咱們感覺不到,覺得是同時進行的。因此說一個線程在start了以後只是處在了能夠運行的狀態,他何時運行仍是由系統來進行調度的。

那一個線程何時會「dead」呢?通常來講當線程對象的run方法執行結束或者在執行中拋出異常的話,那麼這個線程就會結束了。系統會自動對「dead」狀態線程進行清理。

若是一個線程t1在執行的過程當中須要等待另外一個線程t2執行結束後才能運行的話那就能夠在t1在調用t2join()方法

....
def t1(...):
...
t2.join()
...

這樣t1在執行到t2.join()語句後就會等待t2結束後纔會繼續運行。

可是假如t1是個死循環的話那麼等待就沒有意義了,那怎麼辦呢?能夠在調用t2join()方法的時候給一個浮點數作超時參數,這樣這個線程就不會等到花兒也謝了了。我等你10s,你不回來我還不容許我改嫁啊?:)

def t1(...):
...
t2.join(10)
...

若是一個進程的主線程運行完畢而子線程還在執行的話,那麼進程就不會退出,直到全部子線程結束爲止,如何讓主線程結束的時候其餘子線程也乖乖的跟老大撤退呢?那就要把那些不聽話的人設置爲聽話的小弟,使用線程對象的setDaemon()方法,參數爲bool型。True的話就表明你要聽話,我老大(主線程)扯呼,你也要跟着撤,不能拖後腿。若是是False的話就不用那麼聽話了,老大容許大家將在外軍命有所不受的。須要注意的是setDaemon()方法必須在線程對象沒有調用start()方法以前調用,不然沒效果。

t1 = mythread('t1')
print t1.getName(),t1.isDaemon()
t1.setDaemon(True)
print t1.getName(),t1.isDaemon()
t1.start()
print 'main thread exit'

當執行到 print 'main thread exit' 後,主線程就退出了,固然t1這個線程也跟着結束了。可是若是不使用t1線程對象的setDaemon()方法的話,即使主線程結束了,還要等待t1線程本身結束才能退出進程。isDaemon()是用來得到一個線程對象的Daemonflag狀態的。

如何來得到與線程有關的信息呢?

得到當前正在運行的線程的引用

running = threading.currentThread()

得到當前全部活動對象(即run方法開始可是未終止的任何線程)的一個列表

threadlist = threading.enumerate()

得到這個列表的長度

threadcount = threading.activeCount()

查看一個線程對象的狀態調用這個線程對象的isAlive()方法,返回1表明處於「runnable」狀態且沒有「dead

threadflag = threading.isAlive()


 



Python線程編程(二)簡單的線程同步



    多個執行線程常常要共享數據,若是僅僅讀取共享數據還好,可是若是多個線程要修改共享數據的話就可能出現沒法預料的結果。

    假如兩個線程對象t1t2都要對數值num=0進行增1運算,那麼t1t2都各對num修改10次的話,那麼num最終的結果應該爲20。可是若是當t1取得num的值時(假如此時num0),系統把t1調度爲「sleeping」狀態,而此時t2轉換爲「running」狀態,此時t2得到的num的值也爲0,而後他把num+1的值1賦給num。系統又把t2轉化爲「sleeping」狀態,t1爲「running」狀態,因爲t1已經獲得num值爲0,因此他也把num+1的值賦給了num1。原本是2次增1運行,結果倒是num只增了1次。相似這樣的狀況在多線程同時執行的時候是有可能發生的。因此爲了防止這類狀況的出現就要使用線程同步機制。

    最簡單的同步機制就是「鎖」

    鎖對象用threading.RLock類建立

    mylock = threading.RLock()

    如何使用鎖來同步線程呢?線程可使用鎖的acquire() (得到)方法,這樣鎖就進入「locked」狀態。每次只有一個線程能夠得到鎖。若是當另外一個線程試圖得到這個鎖的時候,就會被系統變爲「blocked」狀態,直到那個擁有鎖的線程調用鎖的release() (釋放)方法,這樣鎖就會進入「unlocked」狀態。「blocked」狀態的線程就會收到一個通知,並有權利得到鎖。若是多個線程處於「blocked」狀態,全部線程都會先解除「blocked」狀態,而後系統選擇一個線程來得到鎖,其餘的線程繼續沉默(「blocked」)。

import threading
mylock = threading.RLock()
class mythread(threading.Thread)
    ...
    def run(self ...):
        ...     #此處 不能夠 放置修改共享數據的代碼
        mylock.acquire()
        ...     #此處 能夠 放置修改共享數據的代碼
        mylock.release()
        ...    
#此處 不能夠 放置修改共享數據的代碼

    咱們把修改共享數據的代碼稱爲「臨界區」,必須將全部「臨界區」都封閉在同一鎖對象的acquire()release()方法調用之間。

    鎖只能提供最基本的同步級別。有時須要更復雜的線程同步,例如只在發生某些事件時才訪問一個臨界區(例如當某個數值改變時)。這就要使用「條件變量」。

    條件變量用threading.Condition類建立

    mycondition = threading.Condition()

    條件變量是如何工做的呢?首先一個線程成功得到一個條件變量後,調用此條件變量的wait()方法會致使這個線程釋放這個鎖,並進入「blocked」狀態,直到另外一個線程調用同一個條件變量的notify()方法來喚醒那個進入「blocked」狀態的線程。若是調用這個條件變量的notifyAll()方法的話就會喚醒全部的在等待的線程。

    若是程序或者線程永遠處於「blocked」狀態的話,就會發生死鎖。因此若是使用了鎖、條件變量等同步機制的話,必定要注意仔細檢查,防止死鎖狀況的發生。對於可能產生異常的臨界區要使用異常處理機制中的finally子句來保證釋放鎖。等待一個條件變量的線程必須用notify()方法顯式的喚醒,不然就永遠沉默。保證每個wait()方法調用都有一個相對應的notify()調用,固然也能夠調用notifyAll()方法以防萬一。



 

 

 


Python線程編程(三)同步隊列



咱們常常會採用生產者/消費者關係的兩個線程來處理一個共享緩衝區的數據。例如一個生產者線程接受用戶數據放入一個共享緩衝區裏,等待一個 消費者線程對數據取出處理。可是若是緩衝區的過小而生產者和消費者兩個異步線程的速度不一樣時,容易出現一個線程等待另外一個狀況。爲了儘量的縮短共享資源 並以相同速度工做的各線程的等待時間,咱們可使用一個「隊列」來提供額外的緩衝區。

建立一個「隊列」對象

import Queue
myqueue = Queue.Queue(maxsize = 10)

Queue.Queue類便是一個隊列的同步實現。隊列長度可爲無限或者有限。可經過Queue的構造函數的可選參數maxsize來設定隊列長度。若是maxsize小於1就表示隊列長度無限。

將一個值放入隊列中

myqueue.put(10)

調用隊列對象的put()方法在隊尾插入一個項目。put()有兩個參數,第一個item爲必需的,爲插入項目的值;第二個block爲可選參數,默認爲1。若是隊列當前爲空且block1put()方法就使調用線程暫停,直到空出一個數據單元。若是block0put方法將引起Full異常。

將一個值從隊列中取出

myqueue.get()

調用隊列對象的get()方法從隊頭刪除並返回一個項目。可選參數爲block,默認爲1。若是隊列爲空且block1get()就使調用線程暫停,直至有項目可用。若是block爲0,隊列將引起Empty異常。

咱們用一個例子來展現如何使用Queue

# queue_example.py
from Queue import Queue
import threading
import random
import time

# Producer thread
class Producer(threading.Thread):
def __init__(self, threadname, queue):
threading.Thread.__init__(self, name = threadname)
self.sharedata = queue
def run(self):
for i in range(20):
print self.getName(),'adding',i,'to queue'
self.sharedata.put(i)
time.sleep(random.randrange(10)/10.0)
print self.getName(),'Finished'

# Consumer thread
class Consumer(threading.Thread):
def __init__(self, threadname, queue):
threading.Thread.__init__(self, name = threadname)
self.sharedata = queue
def run(self):
for i in range(20):
print self.getName(),'got a value:',self.sharedata.get()
time.sleep(random.randrange(10)/10.0)
print self.getName(),'Finished'

# Main thread
def main():
queue = Queue()
producer = Producer('Producer', queue)
consumer = Consumer('Consumer', queue)

print 'Starting threads ...'
producer.start()
consumer.start()

producer.join()
consumer.join()

print 'All threads have terminated.'

if __name__ == '__main__':
main()

示例代碼中實現了兩個類:生產者類Producer和消費者類Consumer。前者在一個隨機的時間內放入一個值到隊列queue中而後顯示出來,後者在必定隨機的時間內從隊列queue中取出一個值並顯示出來。