Python多線程(2)——線程同步機制


  本文介紹Python中的線程同步對象,主要涉及 thread 和 threading 模塊。

  threading 模塊提供的線程同步原語包括:Lock、RLock、Condition、Event、Semaphore等對象。

1. Lock

1.1 Lock對象的創建

  Lock是Python中最底層的同步機制,直接由底層模塊 thread 實現,每個lock對象只有兩種狀態——上鎖和未上鎖,不同於下文的RLock對象,Lock對象是不可重入的,也沒有所屬的線程這個概念。

  可以通過下面兩種方式創建一個Lock對象,新創建的 Lock 對象處於未上鎖的狀態:

thread.allocate_lock()
threading.Lock()

  但他們本質上都是在 thread 模塊中實現的。

例如:

>>> l = threading.Lock()
>>> type(l)
<type 'thread.lock'>
>>> l
<thread.lock object at 0x0000000001C8F190>

  

1.2 lock對象的方法

  lock對象提供三種方法:acquire()、locked()和release()

l.acquire(wait=True)

  該函數需要結合參數 wait 進行討論:

  1. 當 wait 是 False 時,如果 l 沒有上鎖,那么acquire()調用將l上鎖,然后返回True;

  2. 當 wait 是 False 時,如果 l 已經上鎖,那么acquire()調用對 l 沒有影響,然后返回False;

  3. 當 wait 是 True 時,如果 l 沒有上鎖,acquire()調用將其上鎖,然后返回True;

  4. 當 wait 是 True 時,如果 l 已經上鎖,此時調用 l.acquire() 的線程將會阻塞,直到其他線程調用 l.release(),這里需要注意的是,就算這個線程是最后一個鎖住 l 的線程,只要它以wait=True調用了acquire(),那它就會阻塞,因為Lock原語是不支持重入的。

  可將,只要 l 沒有上鎖,調用 acquire()的結果是相同的。當l 上鎖了,而 wait=False 時,線程會立即得到一個返回值,不會阻塞在等待鎖上面;而 wait = True時,線程會阻塞等待其他的線程釋放該鎖,所以,一個鎖上面可能有多個處於阻塞等待狀態的線程。

 

l.locked()

  判斷 l 當前是否上鎖,如果上鎖,返回True,否則返回False。

 

l.release()

  解開 l 上的鎖,要求:

  • 任何線程都可以解開一個已經鎖上的Lock對象;
  • l 此時必須處於上鎖的狀態,如果試圖 release() 一個 unlocked 的鎖,將拋出異常 thread.error。

  l一旦通過release()解開,之前等待它(調用過 l.acquire())的所有線程中,只有一個會被立即被喚醒,然后獲得這個鎖。

 

2. RLock 可重入鎖

2.1 RLock對象的創建

  RLock是可重入鎖,提供和lock對象相同的方法,可重入鎖的特點是

  •   記錄鎖住自己的線程 t ,這樣 t 可以多次調用 acquire() 方法而不會被阻塞,比如 t 可以多次聲明自己對某個資源的需求。
  •   可重入鎖必須由鎖住自己的線程釋放(rl.release())
  •   rlock內部有一個計數器,只有鎖住自己的線程 t 調用的 release() 方法和之前調用 acquire() 方法的次數相同時,才會真正解鎖一個rlock。

  通過:

>>> rl = threading.RLock()

  可以創建一個可重入鎖。

 

2.2 rlock對象的方法

  rlock()對象提供和Lock對象相同的acquire()和release()方法。

 

3. Condition 條件變量

3.1 Condition 對象的獲取

  condition對象封裝了一個lock或rlock對象,通過實例化Condition類來獲得一個condition對象:

c = threading.Condition(lock=None)

  正如前面說的,condition對象的是基於Lock對象RLock對象的,如果創建 condition 對象時沒傳入 lock 對象,則會新創建一個RLock對象。

 

3.2 Condition 對象的方法

  Condition對象封裝在一個Lock或RLock對象之上,提供的方法有:acquire(wait=1)、release()、notify()、notifyAll()和wait(timeout=None)

c.acquire(wait=1)
c.release()

  本質上, condition對象的 acquire() 方法和 release() 方法都是底層鎖對象的對應方法,在調用condition對象的其他方法之前,都應該確保線程已經拿到了condition對象對應的鎖,也就是調用過 acquire()。

 

c.notify()
c.notify_all()

  notify()喚醒一個等待 c 的線程,notify_all() 則會喚醒所有等待 c 的線程;

  線程在調用 notify() 和 notifyAll() 之前必須已經獲得 c 對應的鎖,否則拋出 RuntimeError。

  notify() 和 notifyAll() 並不會導致線程釋放鎖,但是notify() 和 notify_all()之后,喚醒了其他的等待線程,當前線程已經准備釋放鎖,因此線程通常會緊接着調用 release() 釋放鎖。

 

c.wait(timeout=None)

  wait()最大的特點是調用wait()的線程必須已經acquire()了 c ,調用wait()將會使這個線程放棄 c,線程在此阻塞,然后當wait()返回時,這個線程往往又拿到了 c 。這個描述比較繞,看一個直觀一點的:

  一個線程想要對臨界資源進行操作,首先要獲得 c ,獲得 c 后,它判斷臨界資源的狀態對不對,發現不對,就調用 wait()放掉手中的 c ,這時候實際上就是在等其他的線程來更新臨界資源的狀態了。當某個其他的線程修改了臨界資源的狀態,然后喚醒等待 c 的線程,這時我們這個線程又拿到 c (假設很幸運地搶到了),就可以繼續執行了。

  如果臨界資源一直不對,而我們這個線程又搶到了 c ,就可以通過一個循環,不斷地釋放掉不需要的鎖,直到臨界資源的狀態符合我們的要求。

例如:

# 消費者
cv.acquire()
while not an_item_is_available():
    cv.wait()
get_an_available_item()
cv.release()

# 生產者
cv.acquire()
make_an_item_available()
cv.notify()
cv.release()

  這個例子中,消費者在產品沒有被生產出來之前,就算拿到 c ,也會立即調用 wait() 釋放,當產品被生產出來后,生產者喚醒一個消費者,消費者重新回到 wait() 阻塞的地方,發現產品已經就緒,於是消費產品,最后釋放 c 。

 

4 Event 事件

4.1 Event 對象的創建

  Event對象可以讓任何數量的線程暫停和等待,event 對象對應一個 True 或 False 的狀態(flag),剛創建的event對象的狀態為False。通過實例化Event類來獲得一個event對象:

e = threading.Event()

  剛創建的event對象 e,它的狀態為 False。

 

4.2 Event 對象的方法

e.clear()

  將 e 的狀態設置為 False。

  

e.set()

  將 e 的狀態設置為 True。

  此時所有等待 e 的線程都被喚醒進入就緒狀態。

 

e.is_set()

  返回 e 的 狀態——True 或 False。

 

e.wait(timeout=None)

  如果 e 的狀態為True,wait()立即返回True,否則線程會阻塞直到超時或者其他的線程調用了e.set()。

 

5. Semaphore 信號量

5.1 Semaphore 對象的創建

  信號量無疑是線程同步中最常用的技術了,信號量是一類通用的鎖,鎖的狀態通常就是真或假,但是信號量有一個初始值,這個值往往反映了固定的資源量。

  通過調用:

s = threading.Semaphore(n=1)

  創建一個Python信號量對象,參數 指定了信號量的初值。

 

5.2 Semaphore對象的方法

s.acquire(wait=True)
  • 當 s 的值 > 0 時,acquire() 會將它的值減 1,同時返回 True。
  • 當 s 的值 = 0 時,需要根據參數 wait 的值進行判斷:如果wait為True,acquire() 會阻塞調用它的線程,直到有其他的線程調用 release() 為止;如果wait為False,acquire() 會立即返回False,告訴調用自己的線程,當前沒有可用的資源。

 

s.release()
  • 當 s 的值 > 0 時,release()會直接將 s 的值加一;
  • 當 s 的值 = 0 時而當前沒有其他等待的線程時,release() 也會將 s 的值加一;
  • 當 s 的值 = 0 時而當前其他等待的線程時,release() 不改變 s 的值(還是0),喚醒任意一個等待信號量的線程;調用release()的線程繼續正常執行。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM