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

來源:互聯網
上載者:User

標籤:

  本文介紹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()的線程繼續正常執行。

 

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.