Python 入門 —— Python threading多線程模塊

Python threading多線程模塊

  • Thread 線程
    線程是操作系統能夠進行運算調度的最小單位(程序執行流的最小單元)
    它被包含在進程之中,是進程中的實際運作單位。一個進程中可以併發多個線程
    每條線程並行執行不同的任務
    (線程是進程中的一個實體,是被系統獨立調度和分派的基本單元)
    每一個進程啓動時都會最先產生一個線程,即主線程
    然後主線程會再創建其他的子線程

1. Thread 類

  • Thread是線程類直接傳入要運行的方法

介紹一個python內置函數 xrange

  • range返回的是一個包含所有元素的列表,xrange返回的是一個生成器,生成器是一個可迭代對象,在對生成器進行迭代時,元素是逐個被創建的。一般來看,在對大序列進行迭代的時候,因爲xrange的特性,所以它會比較節約內存。

Thread 示例:

 import threading
 import time
 # 將要執行的方法作爲參數傳給Thread的構造方法
 def action(arg):
     time.sleep(1)
     print 'the arg is:%s\r' %arg

 for i in xrange(4):
     t =threading.Thread(target=action,args=(i,))
     t.start()

 print 'main thread end!'

執行如下:
這裏寫圖片描述
控制檯顯示如下:
這裏寫圖片描述
按照python解釋器的執行來看,’man thread end!’ 應該是再最後執行,但由於添加線程的原因
,線程字線程的執行和主線程的執行是併發的
這樣看可能不清晰,我們可以再來一個例子

 # 調用threading模塊
 import threading
 from time import ctime, sleep


 def music(a):
     for i in range(2):
         print 'I was listening to %s. %s' % (a,ctime())
         sleep(1)


 def movie(b):
     for i in range(2):
         print 'I was watching to %s. %s' % (b,ctime())
         sleep(5)

 # 建立線程
 t1 = threading.Thread(target=music,args=('告白氣球',))
 # 啓動
 t1.start()
 t2 = threading.Thread(target=movie,args=('泰坦尼克號',))
 t2.start()
 print 'all over %s' % ctime()

執行如下:
這裏寫圖片描述
控制檯顯示如下:
這裏寫圖片描述
由控制檯返回的結果可看出,這幾個線程是並行的

Thread 類中的方法:

構造方法

  • Thread(group=None, target=None, name=None, args=(), kwargs={})
    • group: 線程組,目前還沒有實現,庫引用中提示必須是None;
    • target: 要執行的方法;
    • name: 線程名;
    • args/kwargs: 要傳入方法的參數。

實例方法:

  • get/setName(name): 獲取/設置線程名。
  • is/setDaemon(bool):獲取/設置是後臺線程(默認前臺線程(False))。(在start之前設置)
    如果是後臺線程,主線程執行過程中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,主線程和後臺線程均停止
    如果是前臺線程,主線程執行過程中,前臺線程也在進行,主線程執行完畢後,等待前臺線程也執行完成後,程序停止
  • start():啓動線程。
  • join([timeout]):阻塞當前上下文環境的線程,直到調用此方法的線程終止或到達指定的timeout(可選參數)。
getName 獲取線程名稱
 from threading import Thread
 def Foo(arg):
     print arg

 print 'before'
 t1 = Thread(target=Foo,args=(1,))
 t1.start()
 print t1.getName()   # 顯示t1所代表的線程的名稱 

 t2 = Thread(target=Foo,args=(2,))
 t2.start()
 print t2.getName()   # 顯示t2所代表的線程的名稱

 print 'after'

執行如下:
這裏寫圖片描述
控制檯顯示如下:
這裏寫圖片描述

setDaemon(bool) 設置守護線程與用戶線程

設定守護線程

 from threading import Thread
 import time


 def Test():
     for item in range(100):
         print item
         time.sleep(1)
 print 'before'
 t1 = Thread(target=Test)
 # 爲True表示t1爲守護線程
 # 主線程結束後,t1不管是否執行完都會結束
 t1.setDaemon(True)
 t1.start()
 print 'after'
 time.sleep(10) # 延遲10s結束進程

執行如下:
這裏寫圖片描述
控制檯顯示如下:主線程延時10秒結束,後無論字線程是否完成,都結束
這裏寫圖片描述

設定用戶線程

 from threading import Thread
 import time


 def Test():
     for item in range(100):
         print item
         time.sleep(1)
 print 'before'
 t1 = Thread(target=Test)
 # 爲False表示t1爲用戶線程
 # 主線程結束後,t1還會繼續執行
 t1.setDaemon(False)
 t1.start()
 print 'after'
 time.sleep(10) # 延遲10s結束進程
 print 'end'

執行如下:
這裏寫圖片描述
控制檯顯示如下:
這裏寫圖片描述
主線程結束,字線程仍舊執行

join 方法,阻塞主線程

調用.join()方法後,子線程正常運行,父線程會等待子線程結束後再繼續運行

 from threading import Thread
 import time


 def test():
     for item in range(10):
         print item
         time.sleep(1)
 print 'start'
 t1 = Thread(target=test)
 t1.start()
 # 主線程到join()就不往下走了,直到子線程執行完
 t1.join()   
 print 'end'

執行如下:
這裏寫圖片描述
控制檯顯示如下:
這裏寫圖片描述
若是再join()方法中加入時間,則表示阻塞父線程的時間,時間一到父線程結束
如給join中加上數字5 執行結果如下:
這裏寫圖片描述

2 生產者消費者模型

  • 多線程能幹什麼:
    生產者消費者問題:(經典)
    一直生產 一直消費 中間有閥值 避免供求關係不平衡
  • 生產者消費者的優點(爲什麼經典的設計模式)
    1.解耦(讓程序各模塊之間的關聯性降到最低)
    假設生產者和消費者是兩個類,如果讓生產者直接調用消費者的某個方法,那麼生產者對於消費者就會產生依賴(也就是耦合),
    如果將來消費者的代碼發生變換,可能會影響到生產者,而如果兩者都依賴於某個緩衝區,兩者之間不直接依賴,耦合也就相應降低了
  • 生活中的例子:我們 郵筒 郵遞員
    舉個例子,我們去郵局投遞信件,如果不使用郵筒(也就是緩衝區),你必須得把信直接交給郵遞員,有人會說,直接交給郵遞員不是挺簡單的嘛,其實不簡單,你必須得認識郵遞員,才能把信給他(光憑身上的制服,萬一有人假冒呢???),
    這就產成你和郵遞員之間的依賴了(相當於生產者消費者強耦合),萬一哪天郵遞員換人了,
    你還要重新認識一下(相當於消費者變化導致修改生產者代碼),而郵筒相對來說比較固定,
    你依賴它的成本就比較低(相當於和緩衝區之間的弱耦合)
  • 2.支持併發
    生產者消費者是兩個獨立的併發體,他們之間是用緩衝區作爲橋樑連接,生產者之需要往緩衝區裏丟數據,就可以繼續生產下一個數據,而消費者者只需要從緩衝區裏拿數據即可,這樣就不會因爲彼此速度而發生阻塞接着上面的例子:如果我們不使用郵筒,我們就得在郵局等郵遞員,直到他回來了,我們才能把信給他,這期間我們啥也不能幹(也就是產生阻塞),或者郵遞員挨家挨戶的問(產生論尋)
  • 3.支持忙閒不均如果製造數據的速度時快時慢,緩衝區的好處就體現出來了,當數據製造快的時候,消費者來不及處理,未處理的數據可以暫時存在緩衝區中,等生產者的速度慢下來,消費者再慢慢處理情人節信件太多了,郵遞員一次處理不了,可以放在郵筒中,下次再來取

加一點queue模塊的知識

  • queue——同步的隊列類
    queue模塊實現了多生產者,多消費者的隊列。當 要求信息必須在多線程間安全交換,這個模塊在 線程編程時非常有用 。Queue模塊實現了所有要求的鎖機制。 說了半天就是Queue模塊主要是多線程,保證線程安全使用的。
    這個類實現了三種類型的queue,區別僅僅在於進去和取出的位置。在一個FIFO(First In,First Out)隊列中,先加先取。在一個LIFO(Last In First Out)的隊列中,最後加的先出來(操作起來跟stack一樣)。priority隊列,有序保存,優先級最低的先出來。
    內部實現是在搶佔式線程加上臨時鎖。但是沒有涉及如何去處理線程的重入。
  • 類:class queue.Queue(maxsize = 0)
    構造一個FIFO隊列,maxsize可以限制隊列的大小。如果隊列的大小達到了隊列的上限,就會加鎖,加入就會阻塞,直到隊列的內容被消費掉。maxsize的值小於等於0,那麼隊列的尺寸就是無限制的
  • 隊列對象的方法:
    • Queue.qsize():返回queue的近似值。注意:qsize>0不保證(get)取元素不阻塞。qsize< maxsize不保證(put)存元素不會阻塞
    • Queue.put(item, block=True, timeout=None): 往隊列裏放數據。如果滿了的話,blocking = False 直接報 Full異常。如果blocking = True,就是等一會,timeout必須爲 0 或正數。None爲一直等下去,0爲不等,正數n爲等待n秒還不能存入,報Full異常。
    • Queue.put_nowait(item):往隊列裏存放元素,不等待
    • Queue.get(item, block=True, timeout=None): 從隊列裏取數據。如果爲空的話,blocking = False 直接報 empty異常。如果blocking = True,就是等一會,timeout必須爲 0 或正數。None爲一直等下去,0爲不等,正數n爲等待n秒還不能讀取,報empty異常。
    • Queue.get_nowait(item):從隊列裏取元素,不等待
      兩個方法跟蹤入隊的任務是否被消費者daemon進程完全消費

簡單的數據存取
示例:

 import threading
 # Queue
 import Queue
 import time
 import random

 def producer(name,que):
     # 定義生產者模型
     while True:
         que.put('baozi')
         # 往隊列中放數據
         print '%s made a baozi..' % name
         time.sleep(random.randrange(5))
         # 調用隨機數列,調用的時間爲5內的隨機數

 def consumer(name,que):
     while True:
         que.get()
         print "%s Got a baozi" % name
         time.sleep(random.randrange(3))
 q = Queue.Queue()

 p1 = threading.Thread(target=producer,args=['chef1', q])
 p2 = threading.Thread(target=producer,args=['chef2', q])
 p1.start()
 p2.start()

 c1 = threading.Thread(target=consumer,args=['tom', q])
 c2 = threading.Thread(target=consumer,args=['jarry', q])
 c1.start()
 c2.start()

執行如下:
這裏寫圖片描述
控制檯顯示如下:
這裏寫圖片描述

加上返回隊列大小的限制,將會返回異常,然後通過異常捕獲進行處理
示例:

 import threading
 import Queue
 import time
 import random

 def producer(name, que):
     while True:
         if que.qsize() < 3:
             que.put('')
             print '%s Made a baozi.' % name
         else:
             print '還有三個包子'
         time.sleep (random.randrange(5))

 def consumer(name, que):
     while True:
         try:
             # 加上異常捕獲
             que.get_nowait()
             print '%s:Got a baozi..' % name
         except Exception:
             print '沒有包子了'
         time.sleep(random.randrange(3))
 # 創建隊列
 q = Queue.Queue()

 p1 = threading.Thread(target=producer,args=['chef1',q])
 p2 = threading.Thread(target=producer,args=['chef2',q])
 p1.start()
 p2.start()

 c1 = threading.Thread(target=consumer,args=['tom',q])
 c2 = threading.Thread(target=consumer,args=['harry',q])
 c1.start()
 c2.start()

執行如下:
這裏寫圖片描述
控制檯顯示如下:
這裏寫圖片描述

3 lock

  • 由於線程之間隨機調度:某線程可能在執行n條後,CPU接着執行其他線程。爲了多個線程同時操作一個內存中的資源時不產生混亂,我們使用鎖。
    Lock(指令鎖)是可用的最低級的同步指令。Lock處於鎖定狀態時,不被特定的線程擁有。Lock包含兩種狀態——鎖定和非鎖定,以及兩個基本的方法。
    可以認爲Lock有一個鎖定池,當線程請求鎖定時,將線程至於池中,直到獲得鎖定後出池。池中的線程處於狀態圖中的同步阻塞狀態。
  • 實例方法:
    • acquire([timeout]): 嘗試獲得鎖定。使線程進入同步阻塞狀態。
    • release(): 釋放鎖。使用前線程必須已獲得鎖定,否則將拋出異常。

示例:未加線程鎖,資源混亂

  import threading
 import time 
 num = 0

 def run(n):
     time.sleep(1)
     global num
     num += 1
     print '%s\n ' % num
 for i in range(1500):
     t = threading.Thread(target=run, args=(i,))
     t.start()

執行如下:
這裏寫圖片描述
控制檯顯示如下:要求直到1500,但值顯示到1461
這裏寫圖片描述

加線程鎖

 def run(n):
     time.sleep(1)
     global num
     # 加線程鎖
     lock.acquire()
     num += 1
     print '%s\n ' % num
     # 釋放線程鎖
     lock.release()
 lock = threading.Lock()

 for i in range(1500):
     t = threading.Thread(target=run, args=(i,))
     t.start()

執行如下:
這裏寫圖片描述
控制檯顯示如下:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章