Python 多執行緒 threading和multiprocessing模組

世界看我我看世界發表於2015-11-04

執行緒是一個程式的實體,是由表示程式執行狀態的暫存器(如程式計數器、棧指標)以及堆疊組成,它是比程式更小的單位。
執行緒是程式中的一個執行流。一個執行流是由CPU執行程式程式碼並操作程式的資料所形成的。因此,執行緒被認為是以CPU為主體的行為。
執行緒不包含程式地址空間中的程式碼和資料,執行緒是計算過程在某一時刻的狀態。所以,系統在產生一個執行緒或各個執行緒之間切換時,負擔要比程式小得多。
執行緒是一個使用者級的實體,執行緒結構駐留在使用者空間中,能夠被普通的使用者級函式直接訪問。
一個執行緒本身不是程式,它必須執行於一個程式(程式)之中。因此,執行緒可以定義為一個程式中的單個執行流。
多執行緒是指一個程式中包含多個執行流,多執行緒是實現併發的一種有效手段。一個程式在其執行過程中,可以產生多個執行緒,形成多個執行流。每個執行流即每個執行緒也有它自身的產生、存在和消亡的過程。
多執行緒程式設計的含義就是可以將程式任務分成幾個並行的子任務。

執行緒的狀態圖:

2

Python中常使用的執行緒模組

  • thread(低版本使用的),threading
  • Queue
  • multiprocessing

threading

thread模組是Python低版本中使用的,高版本中被threading代替了。threading模組提供了更方便的API來操作執行緒。

threading.Thread

Thread是threading模組中最重要的類之一,可以使用它來建立執行緒。建立新的執行緒有兩種方法:

  • 方法一:直接建立threading.Thread類的物件,初始化時將可呼叫物件作為引數傳入。
  • 方法二:通過繼承Thread類,重寫它的run方法。

Thread類的構造方法:

__init__(group=None, target=None, name=None, args=(), kwargs=None, verbose=None)

引數說明:
group:執行緒組,目前還沒有實現,庫引用中提示必須是None。
target:要執行的方法;
name:執行緒名;
args/kwargs:要傳入方法的引數。

Thread類擁有的例項方法:

isAlive():返回執行緒是否在執行。正在執行指的是啟動後,終止前。

getName(name)/setName(name):獲取/設定執行緒名。

isDaemon(bool)/setDaemon(bool):獲取/設定是否為守護執行緒。初始值從建立該執行緒的執行緒繼承而來,當沒有非守護執行緒仍在執行時,程式將終止。

start():啟動執行緒。

join([timeout]):阻塞當前上下文環境的執行緒,直到呼叫此方法的執行緒終止或到達指定的等待時間timeout(可選引數)。即當前的執行緒要等呼叫join()這個方法的執行緒執行完,或者是達到規定的時間。

直接建立threading.Thread類的物件

例項:

from threading import Thread
import time
def run(a = None, b = None) :
  print a, b 
  time.sleep(1)

t = Thread(target = run, args = ("this is a", "thread"))
#此時執行緒是新建狀態

print t.getName()#獲得執行緒物件名稱
print t.isAlive()#判斷執行緒是否還活著。
t.start()#啟動執行緒
t.join()#等待其他執行緒執行結束

執行結果:

Thread-1
False
this is a thread

注意:

t = Thread(target = run, args = ("this is a", "thread"))

這句只是建立了一個執行緒,並未執行這個執行緒,此時執行緒處於新建狀態。

t.start()#啟動執行緒

啟動執行緒,此時執行緒扔為執行,只是處於準備狀態。
自定義函式run(),使我們自己根據我們需求自己定義的,函式名可以隨便取,run函式的引數來源於後面的args元組。

通過繼承Thread類

例項:

from threading import Thread
import time

class MyThread(Thread) :
  def __init__(self, a) :
    super(MyThread, self).__init__()
    #呼叫父類的構造方法
    self.a = a

  def run(self) :
    print "sleep :", self.a
    time.sleep(self.a)

t1 = MyThread(2)
t2 = MyThread(4)
t1.start()
t2.start()
t1.join()
t2.join()

執行結果:
4

由於建立了兩個併發執行的執行緒t1和t2,併發執行緒的執行時間不定,誰先執行完的時間也不定,所以執行後列印的結果順序也是不定的。每一次執行都有可能出現不同的結果。

注意:
繼承Thread類的新類MyThread建構函式中必須要呼叫父類的構造方法,這樣才能產生父類的建構函式中的引數,才能產生執行緒所需要的引數。新的類中如果需要別的引數,直接在其構造方法中加即可。
同時,新類中,在重寫父類的run方法時,它預設是不帶引數的,如果需要給它提供引數,需要在類的建構函式中指定,因為線上程執行的過程中,run方法時執行緒自己去呼叫的,不用我們手動呼叫,所以沒法直接給傳遞引數,只能在構造方法中設定好引數,然後再run方法中呼叫。

針對join()函式用法的例項:

# encoding: UTF-8
import threading
import time

def context(tJoin):
    print 'in threadContext.'
    tJoin.start()
    # 將阻塞tContext直到threadJoin終止。
    tJoin.join()
    # tJoin終止後繼續執行。
    print 'out threadContext.'

def join():
    print 'in threadJoin.'
    time.sleep(1)
    print 'out threadJoin.'

tJoin = threading.Thread(target=join)
tContext = threading.Thread(target=context, args=(tJoin,))
tContext.start()

執行結果:

in threadContext.
in threadJoin.
out threadJoin.
out threadContext.

解析:
主程式中這句tJoin = threading.Thread(target=join)執行後,只是建立了一個執行緒物件tJoin,但並未啟動該執行緒。

tContext = threading.Thread(target=context, args=(tJoin,))
tContext.start()

上面這兩句執行後,建立了另一個執行緒物件tContext並啟動該執行緒(列印in threadContext.),同時將tJoin執行緒物件作為引數傳給context函式,在context函式中,啟動了tJoin這個執行緒,同時該執行緒又呼叫了join()函式(tJoin.join()),那tContext執行緒將等待tJoin這執行緒執行完成後,才能繼續tContext執行緒後面的,所以先執行join()函式,列印輸出下面兩句:

in threadJoin.
out threadJoin.

tJoin執行緒執行結束後,繼續執行tContext執行緒,於是列印輸出了out threadContext.,於是就看到我們上面看到的輸出結果,並且無論執行多少次,結果都是這個順序。但如果將context()函式中tJoin.join()這句註釋掉,再執行該程式,列印輸出的結果順序就不定了,因為此時這兩執行緒就是併發執行的。

multiprocessing.dummy

Python中執行緒multiprocessing模組與程式使用的同一模組。使用方法也基本相同,唯一不同的是,from multiprocessing import Pool這樣匯入的Pool表示的是程式池;
from multiprocessing.dummy import Pool這樣匯入的Pool表示的是執行緒池。這樣就可以實現執行緒裡面的併發了。

執行緒池例項:

import time
from multiprocessing.dummy import Pool as ThreadPool
#給執行緒池取一個別名ThreadPool
def run(fn):
  time.sleep(2)
  print fn

if __name__ == '__main__':
  testFL = [1,2,3,4,5]
  pool = ThreadPool(10)#建立10個容量的執行緒池併發執行
  pool.map(run, testFL)
  pool.close()
  pool.join()

執行結果:
5

這裡的pool.map()函式,跟程式池的map函式用法一樣,也跟內建的map函式一樣。

相關文章