多線程threading模塊

python的多線程編程

簡介

多線程編程技術能夠實現代碼並行性,優化處理能力,同時功能的更小劃分可使代碼的可重用性更好。Python中threading和Queue模塊能夠用來實現多線程編程。python

詳解

線程和進程

進程(有時被稱爲重量級進程)是程序的一次執行。每一個進程都有本身的地址空間、內存、數據棧以及其它記錄其運行軌跡的輔助數據。操做系統管理在其上運行的全部進程,併爲這些進程公平地分配時間。進程也能夠經過fork和spawn操做來完成其它的任務,不過各個進程有本身的內存空間、數據棧等,因此只能使用進程間通信(IPC),而不能直接共享信息。
線程(有時被稱爲輕量級進程)跟進程有些類似,不一樣的是全部的線程運行在同一個進程中,共享相同的運行環境。它們能夠想像成是在主進程或「主線程」中並行運行的「迷你進程」。線程有開始、順序執行和結束三部分,它有一個本身的指令指針,記錄本身運行到什麼地方。線程的運行可能被搶佔(中斷)或暫時的被掛起(也叫睡眠)讓其它的線程運行,這叫作讓步。一個進程中的各個線程之間共享同一片數據空間,因此線程之間能夠比進程之間更方便地共享數據以及相互通信。線程通常都是併發執行的,正是因爲這種並行和數據共享的機制使得多個任務的合做變爲可能。實際上,在單CPU的系統中,真正的併發是不可能的,每一個線程會被安排成每次只運行一小會,而後就把CPU讓出來,讓其它的線程去運行。在進程的整個運行過程當中,每一個線程都只作本身的事,在須要的時候跟其它的線程共享運行的結果。多個線程共同訪問同一片數據不是徹底沒有危險的,因爲數據訪問的順序不同,有可能致使數據結果的不一致的問題,這叫作競態條件。而大多數線程庫都帶有一系列的同步原語,來控制線程的執行和數據的訪問。程序員

使用線程

(1)全局解釋器鎖(GIL)
Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。Python在設計之初就考慮到要在主循環中,同時只有一個線程在執行。雖然 Python 解釋器中能夠「運行」多個線程,但在任意時刻只有一個線程在解釋器中運行。
對Python虛擬機的訪問由全局解釋器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個線程在運行。在多線程環境中,Python 虛擬機按如下方式執行:a、設置 GIL;b、切換到一個線程去運行;c、運行指定數量的字節碼指令或者線程主動讓出控制(能夠調用 time.sleep(0));d、把線程設置爲睡眠狀態;e、解鎖 GIL;d、再次重複以上全部步驟。
在調用外部代碼(如 C/C++擴展函數)的時候,GIL將會被鎖定,直到這個函數結束爲止(因爲在這期間沒有Python的字節碼被運行,因此不會作線程切換)編寫擴展的程序員能夠主動解鎖GIL。
(2)退出線程
當一個線程結束計算,它就退出了。線程能夠調用thread.exit()之類的退出函數,也可使用Python退出進程的標準方法,如sys.exit()或拋出一個SystemExit異常等。不過,不能夠直接「殺掉」("kill")一個線程。
不建議使用thread模塊,很明顯的一個緣由是,當主線程退出的時候,全部其它線程沒有被清除就退出了。另外一個模塊threading就能確保全部「重要的」子線程都退出後,進程纔會結束。
(3)Python的線程模塊
Python提供了幾個用於多線程編程的模塊,包括thread、threading和Queue等。thread和threading模塊容許程序員建立和管理線程。thread模塊提供了基本的線程和鎖的支持,threading提供了更高級別、功能更強的線程管理的功能。Queue模塊容許用戶建立一個能夠用於多個線程之間共享數據的隊列數據結構。
避免使用thread模塊,由於更高級別的threading模塊更爲先進,對線程的支持更爲完善,並且使用thread模塊裏的屬性有可能會與threading出現衝突;其次低級別的thread模塊的同步原語不多(實際上只有一個),而threading模塊則有不少;再者,thread模塊中當主線程結束時,全部的線程都會被強制結束掉,沒有警告也不會有正常的清除工做,至少threading模塊能確保重要的子線程退出後進程才退出。編程

threading模塊

thread模塊不支持守護線程,當主線程退出時,全部的子線程不論它們是否還在工做,都會被強行退出。而threading模塊支持守護線程,守護線程通常是一個等待客戶請求的服務器,若是沒有客戶提出請求它就在那等着,若是設定一個線程爲守護線程,就表示這個線程是不重要的,在進程退出的時候,不用等待這個線程退出。若是主線程退出不用等待那些子線程完成,那就設定這些線程的daemon屬性,即在線程thread.start()開始前,調用setDaemon()函數設定線程的daemon標誌(thread.setDaemon(True))就表示這個線程「不重要」。若是想要等待子線程完成再退出,那就什麼都不用作或者顯式地調用thread.setDaemon(False)以保證其daemon標誌爲False,能夠調用thread.isDaemon()函數來判斷其daemon標誌的值。新的子線程會繼承其父線程的daemon標誌,整個Python會在全部的非守護線程退出後纔會結束,即進程中沒有非守護線程存在的時候才結束。
1)threading的Thread類
它有不少thread模塊裏沒有的函數,Thread對象的函數:服務器

201549112739440.png

建立一個Thread的實例,傳給它一個函數

#!/usr/bin/env python
import threading
from time import sleep,ctime
loops=[4,2]
def loop(nloop,nsec):
        print 'start loop',nloop,'at:',ctime()
        sleep(nsec)
        print 'loop',nloop,'done at:',ctime()
def main():
        print '***starting at:',ctime()
        threads=[]
        nloops=range(len(loops))
        for i in nloops:
                t=threading.Thread(target=loop,args=(i,loops[i]))
                threads.append(t)
        for i in nloops:
                threads[i].start()
        for i in nloops:
                threads[i].join()
        print 'all done at',ctime()
if __name__=='__main__':
        main()

運行結果:數據結構

root@hanfeifei-HP-ProDesk-680-G2-MT:/mnt/han# python threading1.py 
***starting at: Sun Jul 31 17:37:39 2016
start loop 0 at: Sun Jul 31 17:37:39 2016
start loop 1 at: Sun Jul 31 17:37:39 2016
loop 1 done at: Sun Jul 31 17:37:41 2016
loop 0 done at: Sun Jul 31 17:37:43 2016
all done at Sun Jul 31 17:37:43 2016

實例化一個Thread(調用 Thread())與調用thread.start_new_thread()之間最大的區別就是,新的線程不會當即開始。在建立線程對象,但不想立刻開始運行線程的時候,這是一個頗有用的同步特性。全部的線程都建立了以後,再一塊兒調用 start()函數啓動,而不是建立一個啓動一個。並且也不用再管理一堆鎖(分配鎖、得到鎖、釋放鎖、檢查鎖的狀態等),只要簡單地對每一個線程調用join()主線程等待子線程的結束便可。join()還能夠設置timeout的參數,即主線程等到超時爲止。
join()的另外一個比較重要的方面是它能夠徹底不用調用,一旦線程啓動後,就會一直運行,直到線程的函數結束,退出爲止。若是主線程除了等線程結束外,還有其它的事情要作,那就不用調用 join(),只有在等待線程結束的時候才調用join()。多線程

建立一個Thread的實例,傳給它一個可調用的類對象

#!/usr/bin/env python
import threading
from time import sleep, ctime
loops=[4,2]
class ThreadFunc(object):
    def __init__(self,func,args,name=''):
        self.name=name
        self.func=func
        self.args=args
    def __call__(self):
        apply(self.func,self.args)
def loop(nloop,nsec):
    print 'start loop',nloop,'at:',ctime()
    sleep(nsec)
    print 'loop',nloop,'done at:',ctime()
def main():
    print 'starting at:',ctime()
    threads=[]
    nloops=range(len(loops))
    for i in nloops:
        t=threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__))
        threads.append(t)
    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print 'all Done at:',ctime()
if __name__=='__main__':
    main()
與傳一個函數很類似的另外一個方法是在建立線程的時候,傳一個可調用的類的實例供線程啓動的時候執行,這是多線程編程的一個更爲面向對象的方法。相對於一個或幾個函數來講,類對象裏可使用類的強大的功能。建立新線程的時候,Thread對象會調用ThreadFunc對象,這時會用到一個特殊函數__call__()。因爲已經有了要用的參數,因此就不用再傳到Thread()的構造函數中。因爲有一個參數的元組,這時要使用apply()函數或使用self.res = self.func(*self.args)。

從Thread派生出一個子類,建立一個這個子類的實例併發

#!/usr/bin/env python 
  
import threading 
from time import sleep, ctime 
  
loops = [ 4, 2 ] 
  
class MyThread(threading.Thread): 
  def __init__(self, func, args, name=''): 
    threading.Thread.__init__(self) 
    self.name = name 
    self.func = func 
    self.args = args 
  
  def getResult(self): 
    return self.res 
  
  def run(self): 
    print 'starting', self.name, 'at:', ctime() 
    self.res = apply(self.func, self.args) 
    print self.name, 'finished at:', ctime() 
  
def loop(nloop, nsec): 
  print 'start loop', nloop, 'at:', ctime() 
  sleep(nsec) 
  print 'loop', nloop, 'done at:', ctime() 
  
def main(): 
  print 'starting at:', ctime() 
  threads = [] 
  nloops = range(len(loops)) 
  
  for i in nloops: 
    t = MyThread(loop, (i, loops[i]), 
    loop.__name__) 
    threads.append(t) 
  
  for i in nloops: 
    threads[i].start() 
  
  for i in nloops: 
    threads[i].join() 
  
  print 'all DONE at:', ctime() 
  
if __name__ == '__main__': 
  main()

子類化Thread類,MyThread子類的構造函數必定要先調用基類的構造函數,特殊函數__call__()在子類中,名字要改成run()。在 MyThread類中,加入一些用於調試的輸出信息,把代碼保存到myThread模塊中,並導入這個類。除使用apply()函數來運行這些函數以外,還能夠把結果保存到實現的self.res屬性中,並建立一個新的函數getResult()來獲得結果。app

Queue模塊

Queue模塊能夠用來進行線程間通信,讓各個線程之間共享數據。Queue解決生產者-消費者的問題,如今建立一個隊列,讓生產者線程把新生產的貨物放進去供消費者線程使用。生產者生產貨物所要花費的時間沒法預先肯定,消費者消耗生產者生產的貨物的時間也是不肯定的。dom

#!/usr/bin/env python 
  
from random import randint 
from time import sleep 
from Queue import Queue 
from myThread import MyThread 
  
def writeQ(queue): 
  print '+++producing object for Q...', 
  queue.put('xxx', 1) 
  print "+++size now:", queue.qsize() 
  
def readQ(queue): 
  val = queue.get(1) 
  print '---consumed object from Q... size now', \ 
    queue.qsize() 
  
def writer(queue, loops): 
  for i in range(loops): 
    writeQ(queue) 
    sleep(randint(1, 3)) 
  
def reader(queue, loops): 
  for i in range(loops): 
    readQ(queue) 
    sleep(randint(2, 5)) 
  
funcs = [writer, reader] 
nfuncs = range(len(funcs)) 
  
def main(): 
  nloops = randint(2, 5) 
  q = Queue(32) 
  
  threads = [] 
  for i in nfuncs: 
    t = MyThread(funcs[i], (q, nloops), \ 
      funcs[i].__name__) 
    threads.append(t) 
  
  for i in nfuncs: 
    threads[i].start() 
  
  for i in nfuncs: 
    threads[i].join() 
  
  print '***all DONE'
  
if __name__ == '__main__': 
  main()

~函數

相關文章
相關標籤/搜索