Python線程指南

來源:互聯網
上載者:User
文章目錄
  • 1.1. 線程狀態
  • 1.2. 線程同步(鎖)
  • 1.3. 線程通訊(條件變數)
  • 1.4. 線程運行和阻塞的狀態轉換
  • 3.1. Thread
  • 3.2. Lock
  • 3.3. RLock
  • 3.4. Condition
  • 3.5. Semaphore/BoundedSemaphore
  • 3.6. Event
  • 3.7. Timer
  • 3.8. local
  • 全文完

本文介紹了Python對於線程的支援,包括“學會”多線程編程需要掌握的基礎以及Python兩個線程標準庫的完整介紹及使用樣本。

注意:本文基於Python2.4完成,;如果看到不明白的詞彙請記得百度Google或維基,whatever。

尊重作者的勞動,轉載請註明作者及原文地址 >.<

1. 線程基礎1.1. 線程狀態

線程有5種狀態,狀態轉換的過程如所示:

1.2. 線程同步(鎖)

多線程的優勢在於可以同時運行多個任務(至少感覺起來是這樣)。但是當線程需要共用資料時,可能存在資料不同步的問題。考慮這樣一種情況:一個列表裡所有元素都是0,線程"set"從後向前把所有元素改成1,而線程"print"負責從前往後讀取列表並列印。那麼,可能線程"set"開始改的時候,線程"print"便來列印列表了,輸出就成了一半0一半1,這就是資料的不同步。為了避免這種情況,引入了鎖的概念。

鎖有兩種狀態——鎖定和未鎖定。每當一個線程比如"set"要訪問共用資料時,必須先獲得鎖定;如果已經有別的線程比如"print"獲得鎖定了,那麼就讓線程"set"暫停,也就是同步阻塞;等到線程"print"訪問完畢,釋放鎖以後,再讓線程"set"繼續。經過這樣的處理,列印列表時要麼全部輸出0,要麼全部輸出1,不會再出現一半0一半1的尷尬場面。

線程與鎖的互動如所示:

1.3. 線程通訊(條件變數)

然而還有另外一種尷尬的情況:列表並不是一開始就有的;而是通過線程"create"建立的。如果"set"或者"print" 在"create"還沒有啟動並執行時候就訪問列表,將會出現一個異常。使用鎖可以解決這個問題,但是"set"和"print"將需要一個無限迴圈——他們不知道"create"什麼時候會運行,讓"create"在運行後通知"set"和"print"顯然是一個更好的解決方案。於是,引入了條件變數。

條件變數允許線程比如"set"和"print"在條件不滿足的時候(列表為None時)等待,等到條件滿足的時候(列表已經建立)發出一個通知,告訴"set" 和"print"條件已經有了,你們該起床幹活了;然後"set"和"print"才繼續運行。

線程與條件變數的互動如所示:

  

1.4. 線程運行和阻塞的狀態轉換

最後看看線程運行和阻塞狀態的轉換。

阻塞有三種情況:
同步阻塞是指處於競爭鎖定的狀態,線程請求鎖定時將進入這個狀態,一旦成功獲得鎖定又恢複到運行狀態;
等待阻塞是指等待其他線程通知的狀態,線程獲得條件鎖定後,調用“等待”將進入這個狀態,一旦其他線程發出通知,線程將進入同步阻塞狀態,再次競爭條件鎖定;
而其他阻塞是指調用time.sleep()、anotherthread.join()或等待IO時的阻塞,這個狀態下線程不會釋放已獲得的鎖定。

tips: 如果能理解這些內容,接下來的主題將是非常輕鬆的;並且,這些內容在大部分流行的程式設計語言裡都是一樣的。(意思就是非看懂不可 >_< 嫌作者水平低找別人的教程也要看懂)

2. thread

Python通過兩個標準庫thread和threading提供對線程的支援。thread提供了低層級的、原始的線程以及一個簡單的鎖。

# encoding: UTF-8import threadimport time# 一個用於線上程中執行的函數def func():    for i in range(5):        print 'func'        time.sleep(1)       # 結束當前線程    # 這個方法與thread.exit_thread()等價    thread.exit() # 當func返回時,線程同樣會結束       # 啟動一個線程,線程立即開始運行# 這個方法與thread.start_new_thread()等價# 第一個參數是方法,第二個參數是方法的參數thread.start_new(func, ()) # 方法沒有參數時需要傳入空tuple# 建立一個鎖(LockType,不能直接執行個體化)# 這個方法與thread.allocate_lock()等價lock = thread.allocate()# 判斷鎖是鎖定狀態還是釋放狀態print lock.locked()# 鎖通常用於控制對共用資源的訪問count = 0# 獲得鎖,成功獲得鎖定後返回True# 可選的timeout參數不填時將一直阻塞直到獲得鎖定# 否則逾時後將返回Falseif lock.acquire():    count += 1       # 釋放鎖    lock.release()# thread模組提供的線程都將在主線程結束後同時結束time.sleep(6)

thread 模組提供的其他方法:

thread.interrupt_main(): 在其他線程中終止主線程。

thread.get_ident(): 獲得一個代表當前線程的魔法數字,常用於從一個字典中獲得線程相關的資料。這個數字本身沒有任何含義,並且當線程結束後會被新線程複用。

thread還提供了一個ThreadLocal類用於管理線程相關的資料,名為 thread._local,threading中引用了這個類。

由於thread提供的線程功能不多,無法在主線程結束後繼續運行,不提供條件變數等等原因,一般不使用thread模組,這裡就不多介紹了。

3. threading

threading基於Java的執行緒模式設計。鎖(Lock)和條件變數(Condition)在Java中是對象的基本行為(每一個對象都內建了鎖和條件變數),而在Python中則是獨立的對象。Python Thread提供了Java Thread的行為的子集;沒有優先順序、線程組,線程也不能被停止、暫停、恢複、中斷。Java Thread中的部分被Python實現了的靜態方法在threading中以模組方法的形式提供。

threading 模組提供的常用方法:

threading.currentThread(): 返回當前的線程變數。

threading.enumerate(): 返回一個包含正在啟動並執行線程的list。正在運行指線程啟動後、結束前,不包括啟動前和終止後的線程。

threading.activeCount(): 返回正在啟動並執行線程數量,與len(threading.enumerate())有相同的結果。

threading模組提供的類: 
Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local.

3.1. Thread

Thread是線程類,與Java類似,有兩種使用方法,直接傳入要啟動並執行方法或從Thread繼承並覆蓋run():

# encoding: UTF-8import threading# 方法1:將要執行的方法作為參數傳給Thread的構造方法def func():    print 'func() passed to Thread't = threading.Thread(target=func)t.start()# 方法2:從Thread繼承,並重寫run()class MyThread(threading.Thread):    def run(self):        print 'MyThread extended from Thread't = MyThread()t.start()

構造方法:

Thread(group=None, target=None, name=None, args=(), kwargs={})

group: 線程組,目前還沒有實現,庫引用中提示必須是None;

target: 要執行的方法;

name: 線程名;

args/kwargs: 要傳入方法的參數。

執行個體方法:

isAlive(): 返回線程是否在運行。正在運行指啟動後、終止前。

get/setName(name): 擷取/設定線程名。

is/setDaemon(bool): 擷取/設定是否守護線程。初始值從建立該線程的線程繼承。當沒有非守護線程仍在運行時,程式將終止。

start(): 啟動線程。

join([timeout]): 阻塞當前上下文環境的線程,直到調用此方法的線程終止或到達指定的timeout(選擇性參數)。

一個使用join()的例子:

# encoding: UTF-8import threadingimport timedef 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.

3.2. Lock

Lock(指令鎖)是可用的最低級的同步指令。Lock處於鎖定狀態時,不被特定的線程擁有。Lock包含兩種狀態——鎖定和非鎖定,以及兩個基本的方法。

可以認為Lock有一個鎖定池,當線程請求鎖定時,將線程至於池中,直到獲得鎖定後出池。池中的線程處於狀態圖中的同步阻塞狀態。

構造方法:

Lock()

執行個體方法:

acquire([timeout]): 使線程進入同步阻塞狀態,嘗試獲得鎖定。

release(): 釋放鎖。使用前線程必須已獲得鎖定,否則將拋出異常。

# encoding: UTF-8import threadingimport timedata = 0lock = threading.Lock()def func():    global data    print '%s acquire lock...' % threading.currentThread().getName()       # 調用acquire([timeout])時,線程將一直阻塞,    # 直到獲得鎖定或者直到timeout秒後(timeout參數可選)。    # 返回是否獲得鎖。    if lock.acquire():        print '%s get the lock.' % threading.currentThread().getName()        data += 1        time.sleep(2)        print '%s release lock...' % threading.currentThread().getName()               # 調用release()將釋放鎖。        lock.release()t1 = threading.Thread(target=func)t2 = threading.Thread(target=func)t3 = threading.Thread(target=func)t1.start()t2.start()t3.start() 
3.3. RLock

RLock(可重新進入鎖)是一個可以被同一個線程請求多次的同步指令。RLock使用了“擁有的線程”和“遞迴等級”的概念,處於鎖定狀態時,RLock被某個線程擁有。擁有RLock的線程可以再次調用acquire(),釋放鎖時需要調用release()相同次數。

可以認為RLock包含一個鎖定池和一個初始值為0的計數器,每次成功調用 acquire()/release(),計數器將+1/-1,為0時鎖處於未鎖定狀態。

構造方法:

RLock()

執行個體方法:

acquire([timeout])/release(): 跟Lock差不多。

# encoding: UTF-8import threadingimport timerlock = threading.RLock()def func():    # 第一次請求鎖定    print '%s acquire lock...' % threading.currentThread().getName()    if rlock.acquire():        print '%s get the lock.' % threading.currentThread().getName()        time.sleep(2)               # 第二次請求鎖定        print '%s acquire lock again...' % threading.currentThread().getName()        if rlock.acquire():            print '%s get the lock.' % threading.currentThread().getName()            time.sleep(2)               # 第一次釋放鎖        print '%s release lock...' % threading.currentThread().getName()        rlock.release()        time.sleep(2)               # 第二次釋放鎖        print '%s release lock...' % threading.currentThread().getName()        rlock.release()t1 = threading.Thread(target=func)t2 = threading.Thread(target=func)t3 = threading.Thread(target=func)t1.start()t2.start()t3.start()
3.4. Condition

Condition(條件變數)通常與一個鎖關聯。需要在多個Contidion中共用一個鎖時,可以傳遞一個Lock/RLock執行個體給構造方法,否則它將自己產生一個RLock執行個體。

可以認為,除了Lock帶有的鎖定池外,Condition還包含一個等待池,池中的線程處於狀態圖中的等待阻塞狀態,直到另一個線程調用notify()/notifyAll()通知;得到通知後線程進入鎖定池等待鎖定。

構造方法:

Condition([lock/rlock])

執行個體方法:

acquire([timeout])/release(): 調用關聯的鎖的相應方法。

wait([timeout]): 調用這個方法將使線程進入Condition的等待池等待通知,並釋放鎖。使用前線程必須已獲得鎖定,否則將拋出異常。

notify(): 調用這個方法將從等待池挑選一個線程並通知,收到通知的線程將自動調用acquire()嘗試獲得鎖定(進入鎖定池);其他線程仍然在等待池中。調用這個方法不會釋放鎖定。使用前線程必須已獲得鎖定,否則將拋出異常。

notifyAll(): 調用這個方法將通知等待池中所有的線程,這些線程都將進入鎖定池嘗試獲得鎖定。調用這個方法不會釋放鎖定。使用前線程必須已獲得鎖定,否則將拋出異常。

例子是很常見的生產者/消費者模式:

# encoding: UTF-8import threadingimport time# 商品product = None# 條件變數con = threading.Condition()# 生產者方法def produce():    global product       if con.acquire():        while True:            if product is None:                print 'produce...'                product = 'anything'                               # 通知消費者,商品已經生產                con.notify()                       # 等待通知            con.wait()            time.sleep(2)# 消費者方法def consume():    global product       if con.acquire():        while True:            if product is not None:                print 'consume...'                product = None                               # 通知生產者,商品已經沒了                con.notify()                       # 等待通知            con.wait()            time.sleep(2)t1 = threading.Thread(target=produce)t2 = threading.Thread(target=consume)t2.start()t1.start()
3.5. Semaphore/BoundedSemaphore

Semaphore(訊號量)是電腦科學史上最古老的同步指令之一。Semaphore管理一個內建的計數器,每當調用acquire()時-1,調用release() 時+1。計數器不能小於0;當計數器為0時,acquire()將阻塞線程至同步鎖定狀態,直到其他線程調用release()。

基於這個特點,Semaphore經常用來同步一些有“訪客上限”的對象,比如串連池。

BoundedSemaphore 與Semaphore的唯一區別在於前者將在調用release()時檢查計數器的值是否超過了計數器的初始值,如果超過了將拋出一個異常。

構造方法:

Semaphore(value=1): value是計數器的初始值。

執行個體方法:

acquire([timeout]): 請求Semaphore。如果計數器為0,將阻塞線程至同步阻塞狀態;否則將計數器-1並立即返回。

release(): 釋放Semaphore,將計數器+1,如果使用BoundedSemaphore,還將進行釋放次數檢查。release()方法不檢查線程是否已獲得 Semaphore。

# encoding: UTF-8import threadingimport time# 計數器初值為2semaphore = threading.Semaphore(2)def func():       # 請求Semaphore,成功後計數器-1;計數器為0時阻塞    print '%s acquire semaphore...' % threading.currentThread().getName()    if semaphore.acquire():               print '%s get semaphore' % threading.currentThread().getName()        time.sleep(4)               # 釋放Semaphore,計數器+1        print '%s release semaphore' % threading.currentThread().getName()        semaphore.release()t1 = threading.Thread(target=func)t2 = threading.Thread(target=func)t3 = threading.Thread(target=func)t4 = threading.Thread(target=func)t1.start()t2.start()t3.start()t4.start()time.sleep(2)# 沒有獲得semaphore的主線程也可以調用release# 若使用BoundedSemaphore,t4釋放semaphore時將拋出異常print 'MainThread release semaphore without acquire'semaphore.release()
3.6. Event

Event(事件)是最簡單的線程通訊機制之一:一個線程通知事件,其他線程等待事件。Event內建了一個初始為False的標誌,當調用set()時設為True,調用clear()時重設為 False。wait()將阻塞線程至等待阻塞狀態。

Event其實就是一個簡化版的 Condition。Event沒有鎖,無法使線程進入同步阻塞狀態。

構造方法:

Event()

執行個體方法:

isSet(): 當內建標誌為True時返回True。

set(): 將標誌設為True,並通知所有處於等待阻塞狀態的線程恢複運行狀態。

clear(): 將標誌設為False。

wait([timeout]): 如果標誌為True將立即返回,否則阻塞線程至等待阻塞狀態,等待其他線程調用set()。

# encoding: UTF-8import threadingimport timeevent = threading.Event()def func():    # 等待事件,進入等待阻塞狀態    print '%s wait for event...' % threading.currentThread().getName()    event.wait()       # 收到事件後進入運行狀態    print '%s recv event.' % threading.currentThread().getName()t1 = threading.Thread(target=func)t2 = threading.Thread(target=func)t1.start()t2.start()time.sleep(2)# 發送事件通知print 'MainThread set event.'event.set()
3.7. Timer

Timer(定時器)是Thread的衍生類別,用於在指定時間後調用一個方法。

構造方法:

Timer(interval, function, args=[], kwargs={})

interval: 指定的時間

function: 要執行的方法

args/kwargs: 方法的參數

執行個體方法:

Timer從Thread派生,沒有增加執行個體方法。

# encoding: UTF-8import threadingdef func():    print 'hello timer!'timer = threading.Timer(5, func)timer.start()
3.8. local

local是一個小寫字母開頭的類,用於管理 thread-local(線程局部的)資料。對於同一個local,線程無法訪問其他線程設定的屬性;線程設定的屬性不會被其他線程設定的同名屬性替換。

可以把local看成是一個“線程-屬性字典”的字典,local封裝了從自身使用線程作為 key檢索對應的屬性字典、再使用屬性名稱作為key檢索屬性值的細節。

# encoding: UTF-8import threadinglocal = threading.local()local.tname = 'main'def func():    local.tname = 'notmain'    print local.tnamet1 = threading.Thread(target=func)t1.start()t1.join()print local.tname

熟練掌握Thread、Lock、Condition就可以應對絕大多數需要使用線程的場合,某些情況下local也是非常有用的東西。本文的最後使用這幾個類展示線程基礎中提到的情境:

# encoding: UTF-8import threadingalist = Nonecondition = threading.Condition()def doSet():    if condition.acquire():        while alist is None:            condition.wait()        for i in range(len(alist))[::-1]:            alist[i] = 1        condition.release()def doPrint():    if condition.acquire():        while alist is None:            condition.wait()        for i in alist:            print i,        print        condition.release()def doCreate():    global alist    if condition.acquire():        if alist is None:            alist = [0 for i in range(10)]            condition.notifyAll()        condition.release()tset = threading.Thread(target=doSet,name='tset')tprint = threading.Thread(target=doPrint,name='tprint')tcreate = threading.Thread(target=doCreate,name='tcreate')tset.start()tprint.start()tcreate.start()
全文完
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.