小豬的Python學習之旅 —— 11.Python併發之threading模組(2)
小豬的Python學習之旅 —— 11.Python併發之threading模組(2)
標籤:Python
一句話概括本文:
本節繼續把Python裡threading執行緒模組剩下的Condition,Semaphore,
Event,Timer和Barrier講解完畢,文件是枯燥無味的,希望通過簡單
有趣的例子,可以幫你快速掌握這幾個東東的用法~
啃文件是比較乏味的,先來個小姐姐提提神吧~
別問高清原圖,程式猿自己動手,豐(營)衣(養)足(跟)食(不上),
指令碼自取:https://github.com/coder-pig/ReptileSomething
引言:
如果你忘記了threading上一部分內容,可以移步至:
小豬的Python學習之旅 —— 7.Python併發之threading模組(1)
溫故知新,官方文件依舊是:
https://docs.python.org/3/library/threading.html
1.條件變數(Condition)
上節學習了Python為我們提供的第一個用於執行緒同步的東東——互斥鎖,
又分Lock(指令鎖)與RLock(可重入鎖),但是互斥鎖只是最簡單的同步機制,
Python為我們提供了Condition(條件變數),以便於處理複雜執行緒同步問題,
比如最經典的生產者與消費者問題。
Condition除了提供與Lock類似的acquire()與release()函式外,還提供了
wait()與notify()函式。用法如下:
- 1.呼叫threading.Condition獲得一個條件變數物件;
- 2.執行緒呼叫acquire獲得Condition物件;
- 3.進行條件判斷,不滿足條件呼叫wait函式,滿足條件,進行一些處理改變
條件後,呼叫notify函式通知處於wait狀態的執行緒,重新進行條件判斷。
寫個簡單的生產者與消費者例子體驗下:
import threading
import time
condition = threading.Condition()
products = 0 # 商品數量
# 定義生產者執行緒類
class Producer(threading.Thread):
def run(self):
global products
while True:
if condition.acquire():
if products >= 99:
condition.wait()
else:
products += 2
print(self.name + "生產了2個產品,當前剩餘產品數為:" + str(products))
condition.notify()
condition.release()
time.sleep(2)
# 定義消費者執行緒類
class Consumer(threading.Thread):
def run(self):
global products
while True:
if condition.acquire():
if products < 3:
condition.wait()
else:
products -= 3
print(self.name + "消耗了3個產品,當前剩餘產品數為:" + str(products))
condition.notify()
condition.release()
time.sleep(2)
if __name__ == '__main__':
# 建立五個生產者執行緒
for i in range(5):
p = Producer()
p.start()
# 建立兩個消費者執行緒
for j in range(2):
c = Consumer()
c.start()
執行結果:
Condition維護著一個互斥鎖物件(預設是RLock),也可以自己例項化一個
在Condition例項化的時候通過建構函式傳入,SO,呼叫的Condition的
acquire與release函式,其實呼叫就是這個鎖物件的acquire與release函式。
下面詳解下除了acquire與release函式外Condition提供的相關函式吧:
(注:下述方法只有在acquire之後才能呼叫,不然會報RuntimeError異常)
- wait(timeout=None):釋放鎖,同時執行緒被掛起,直到收到通知被喚醒
或超時(如果設定了timeout),當執行緒被喚醒並重新佔有鎖時,程式才繼續執行; - wait_for(predicate, timeout=None):等待知道條件為True,predicate應該是
一個回撥函式,返回布林值,timeout用於指定超時時間,返回值為回撥函式
返回的布林值,或者超時,返回False(3.2新增); - notify(n=1):預設喚醒一個正在的等待執行緒,notify並不釋放鎖!!!
- notify_all():喚醒所有等待執行緒,進入就緒狀態,等待獲得鎖,notify_all 同樣不釋放鎖!!!
2.訊號量(Semaphore)
訊號量,也是一個很容易懂的東西,舉個簡單的例子:
假如廁所裡有五個蹲坑,有人來開大,就會佔用一個坑位,
所剩餘的坑位-1,當五個坑都被人佔滿的時候,新來的人
就只能在外面等,直到有人出來為止。
這裡的五個糞坑就是訊號量,蹲坑的人就是執行緒,
初始值為5,來人-1,走人+1;超過初始值,新來的處於堵塞狀態;
原理很簡單,試試看下原始碼:
看下_init_方法
傳入引數value,預設值為1,不能傳入負數,否則拋ValueError異常;
建立了一個Condition條件變數,傳入一個Lock例項;
接著看下acquire函式:
- 先是判斷,如果沒有加鎖然後設定了超時時間,丟擲ValueError;
- 迴圈,如果value == 0,沒有加鎖或在超時時間內,跳出迴圈;
否則,呼叫Condition變數wait函式等待通知或超時; - 如果value不為0,跳出迴圈執行else裡的程式碼,訊號量-1,rc = ture,
代表可以呼叫release函式,最後返回rc;
再接著是release函式,更簡單
訊號量+1,然後呼叫Condition變數的notify喚醒一個執行緒~
剩下的_enter_和_exit_就不用說了,重寫這兩個方法就能直接用with關鍵字了
就是那麼簡單,把我們蹲坑的那個例子寫成程式碼吧:
import threading
import time
import random
s = threading.Semaphore(5) # 糞坑
class Human(threading.Thread):
def run(self):
s.acquire() # 佔坑
print("拉屎拉屎 - " + self.name + " - " + str(time.ctime()))
time.sleep(random.randrange(1, 3))
print("拉完走人 - " + self.name + " - " + str(time.ctime()))
s.release() # 走人
if __name__ == '__main__':
for i in range(10):
human = Human()
human.start()
輸出結果:
3.通用的條件變數(Event)
Python提供的用於執行緒間通訊的訊號標誌,一個執行緒標識了一個事件,
其他執行緒處於等待狀態,直到事件發生後,所有執行緒都會被啟用。
Event物件屬性實現了簡單的執行緒通訊機制,提供了設定訊號,清楚訊號,
等待等用於實現執行緒間的通訊。提供以下四個可供呼叫的方法:
- is_set():判斷內部標誌是否為真
- set():設定訊號標誌為真
- clear():清除Event物件內部的訊號標誌(設定為false)
- wait(timeout=None):使執行緒一直處於堵塞,知道識別符號變為True
感覺有點蒙圈,看一波原始碼吧~
先是_init_函式
又是用到Condition條件變數,還有設定了一個_flag = False,這個就是標記吧!
is_set函式比較簡單,返回_flag,
然後是set()函式:
加鎖,然後設定_flag為true,然後notify_all喚醒所有執行緒,最後釋放鎖,
簡單,接著clear函式呢?
註釋的意思是:重置內部標記為false,隨後,呼叫wait()的執行緒將被堵塞,
直到呼叫set()將內部標記再次設定為true。也很簡單,最後是wait方法:
判斷標誌是否為False,False的話進入堵塞狀態,(⊙v⊙)嗯
原始碼就那麼簡單,感覺看完還是蒙圈不知道怎麼用,寫個簡單的例子?
汽車過紅綠燈的例子:
import threading
import time
import random
class CarThread(threading.Thread):
def __init__(self, event):
threading.Thread.__init__(self)
self.threadEvent = event
def run(self):
# 休眠模擬汽車先後到達路口時間
time.sleep(random.randrange(1, 10))
print("汽車 - " + self.name + " - 到達路口...")
self.threadEvent.wait()
print("汽車 - " + self.name + " - 通過路口...")
if __name__ == '__main__':
light_event = threading.Event()
# 假設有20臺車子
for i in range(20):
car = CarThread(event=light_event)
car.start()
while threading.active_count() > 1:
light_event.clear()
print("紅燈等待...")
time.sleep(3)
print("綠燈通行...")
light_event.set()
time.sleep(2)
輸出結果:
4.定時器(Timer)
與Thread類似,只是要等待一段時間後才會開始執行,單位秒,用法也很簡單:
import threading
import time
def skill_ready():
print("!!!!!!大招已經準備好了!!!!!!")
if __name__ == '__main__':
t = threading.Timer(5, skill_ready)
t.start()
while threading.active_count() > 1:
print("======大招蓄力中======")
time.sleep(1)
輸出結果:
5.柵欄(Barrier)
Barrier直譯柵欄,感覺不是很好理解,網上有個形象化的例子,把他比喻
成賽馬用的柵欄,然後馬(執行緒)依次來到柵欄前等待(wait),直到所有的馬
都停在柵欄面前了,然後所有馬開始同時出發(start)。
簡單點說就是,多個執行緒間的相互等待,呼叫了wait()方法的執行緒進入堵塞,
直到所有的執行緒都呼叫了wait()方法,然後所有執行緒同時進行就緒狀態,
等待排程執行。
建構函式:
Barrier(parties,action=None,timeout=None)
- parties:建立一個可容納parties條執行緒的柵欄;
- action:全部執行緒被釋放時可被其中一條執行緒呼叫的可呼叫物件;
- timeout:執行緒呼叫wait()方法時沒有顯式設定timeout,就用的這個作為預設值;
相關函式:
- wait(timeout=None):表示執行緒就位,返回值是一個0到parties-1之間的整數,
每條執行緒都不一樣,這個值可以用作挑選一條執行緒做些清掃工作,另外如果你在
建構函式裡設定了action的話,其中一個執行緒在釋放之前將會呼叫它。如果呼叫
出錯的話,會讓柵欄進入broken狀態,超時同樣也會進入broken狀態,如果柵欄
在處於broke狀態的時候呼叫reset函式,會丟擲一個BrokenBarrierError異常。 - reset():本方法將柵欄置為初始狀態,即empty狀態。所有已經在等待的執行緒
都會接收到BrokenBarrierError異常,注意當有其他處於unknown狀態的執行緒時,
呼叫此方法將可能獲取到額外的訪問。因此如果一個柵欄進入了broken狀態,
最好是放棄他並新建一個柵欄,而不是呼叫reset方法。 - abort():將柵欄置為broken狀態。本方法將使所有正在等待或將要呼叫
wait()方法的執行緒收到BrokenBarrierError異常。本方法的使用情景為,比如:
有一條執行緒需要abort(),又不想給其他執行緒造成死鎖的狀態,或許設定
timeout引數要比使用本方法更可靠。 - parites:將要使用本 barrier 的執行緒的數量
- n_waiting:正在等待本 barrier 的執行緒的數量
- broken:柵欄是否為broken狀態,返回一個布林值
BrokenBarrierError:RuntimeError的子類,當柵欄被reset()或broken時引發;
(感覺都不知所云,寫個簡單的例子來熟悉下用法吧~)
例子:公司一起去旅遊
import threading
import time
import random
class Staff(threading.Thread):
def __init__(self, barriers):
threading.Thread.__init__(self)
self.barriers = barriers
def run(self):
print("員工 【" + self.name + "】" + "出門")
time.sleep(random.randrange(1, 10))
print("員工 【" + self.name + "】" + "已簽到")
self.barriers.wait()
def ready():
print(threading.current_thread().name + ":人齊,出發,出發~~~")
if __name__ == '__main__':
print("要出去旅遊啦,大家快集合~")
b = threading.Barrier(10, action=ready, timeout=20)
for i in range(10):
staff = Staff(b)
staff.start()
執行結果:
PS:這裡可以試下設定超時,還有修改ready方法,故意引起異常,
然後會丟擲BrokenBarrierError異常。
6.小結
加了下班,終於把threading模組啃完了,不然爬小姐姐好玩,有成就感,
當然Python執行緒肯定不止那麼簡單,後面還有佇列這些東西~慢慢來,不急。
下一節開始摳multiprocessing這個程式模組,又是塊大骨頭,敬請期待~
本節參考文獻:
來啊,Py交易啊
想加群一起學習Py的可以加下,智障機器人小Pig,驗證資訊裡包含:
Python,python,py,Py,加群,交易,屁眼 中的一個關鍵詞即可通過;
驗證通過後回覆 加群 即可獲得加群連結(不要把機器人玩壞了!!!)~~~
歡迎各種像我一樣的Py初學者,Py大神加入,一起愉快地交流學♂習,van♂轉py。
相關文章
- 小豬的Python學習之旅 —— 7.Python併發之threading模組(1)Pythonthread
- 學會使用Python的threading模組、掌握併發程式設計基礎Pythonthread程式設計
- 小豬的Python學習之旅 —— 18.Python微信轉發小宇宙早報Python
- 小豬的Python學習之旅 —— 3.正規表示式Python
- python之 threading(多執行緒)模組Pythonthread執行緒
- 小豬的Python學習之旅 —— 1.基礎知識儲備Python
- Python學習之模組Python
- Python多執行緒之_thread與threading模組Python執行緒thread
- 小豬的Python學習之旅 —— 22.安靜!吵到我用TNT了Python
- python開發學習之如何更好的引用Python模組?Python
- Python學習之 datetime模組Python
- Python學習之常用模組Python
- python threading模組有哪些函式Pythonthread函式
- Python學習之模組與包Python
- # 小豬的Python學習之旅 —— 17.Python資料分析:我主良緣交友瞭解下Python
- python中socket+threading的自定義併發Pythonthread
- 小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐Python爬蟲
- 豬行天下之Python基礎——10.2 Python常用模組(下)Python
- 豬行天下之Python基礎——10.1 Python常用模組(上)Python
- [Python模組學習] glob模組Python
- 模組學習之hashlib模組
- 模組學習之logging模組
- Python學習之如何引用Python自定義模組?Python
- 小豬的Python學習之旅 —— 20.抓取Gank.io所有資料儲存到MySQL中PythonMySql
- 學習Python的urllib模組Python
- 豬行天下之Python基礎——2.1 Python註釋與模組Python
- Python開發常用的庫及模組!Python學習教程Python
- Python學習——logging模組Python
- Python學習——shelve模組Python
- Python學習——xml模組PythonXML
- Python學習——configparser模組Python
- Python學習——hashlib模組Python
- nginx學習之模組Nginx
- 小豬的Python學習之旅 —— 8.爬蟲實戰:刷某部落格站點的訪問量Python爬蟲
- 小豬的Python學習之旅 —— 19.Python微信自動好友驗證,自動回覆,傳送群聊連結Python
- Python的shutil zipfile tarfile模組學習Python
- 小豬的Python學習之旅 —— 16.再嘗Python資料分析:採集拉勾網資料分析Android就業行情PythonAndroid就業
- 小豬的Python學習之旅 —— 15.淺嘗Python資料分析:分析2018政府工作報告中的高頻詞Python
- Python學習之Web開發及圖形使用者介面模組!PythonWeb