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

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

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

1. Lockui

1.1 Lock對象的建立spa

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

  能夠經過下面兩種方式建立一個Lock對象,新建立的 Lock 對象處於未上鎖的狀態:對象

thread.allocate_lock()
threading.Lock()

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

例如:事件

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

  

1.2 lock對象的方法資源

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

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()的線程繼續正常執行。
相關文章
相關標籤/搜索