Python 併發程式設計(四):詳解 Python 執行緒訊息通訊機制
前面我已經向大家介紹了,如何使用建立執行緒,啟動執行緒。相信大家都會有這樣一個想法,執行緒無非就是建立一下,然後再start()
下,實在是太簡單了。
可是要知道,在真實的專案中,實際場景可要我們舉的例子要複雜的多得多,不同執行緒的執行可能是有順序的,或者說他們的執行是有條件的,是要受控制的。如果僅僅依靠前面學的那點淺薄的知識,是遠遠不夠的。
那今天,我們就來探討一下如何控制執行緒的觸發執行。
要實現對多個執行緒進行控制,其實本質上就是訊息通訊機制在起作用,利用這個機制傳送指令,告訴執行緒,什麼時候可以執行,什麼時候不可以執行,執行什麼內容。
經過我的總結,執行緒中通訊方法大致有如下三種:
- threading.Event
- threading.Condition
- queue.Queue
接下來我們來一一探討下。
2.4.1 Event事件
Python提供了非常簡單的通訊機制 Threading.Event
,通用的條件變數。多個執行緒可以等待某個事件的發生
,在事件發生後,所有的執行緒
都會被啟用
。
關於Event的使用也超級簡單,就三個函式
event = threading.Event()
# 重置event,使得所有該event事件都處於待命狀態
event.clear()
# 等待接收event的指令,決定是否阻塞程式執行
event.wait()
# 傳送event指令,使所有設定該event事件的執行緒執行
event.set()
舉個例子來看下。
import time
import threading
class MyThread(threading.Thread):
def __init__(self, name, event):
super().__init__()
self.name = name
self.event = event
def run(self):
print('Thread: {} start at {}'.format(self.name, time.ctime(time.time())))
# 等待event.set()後,才能往下執行
self.event.wait()
print('Thread: {} finish at {}'.format(self.name, time.ctime(time.time())))
threads = []
event = threading.Event()
# 定義五個執行緒
[threads.append(MyThread(str(i), event)) for i in range(1,5)]
# 重置event,使得event.wait()起到阻塞作用
event.clear()
# 啟動所有執行緒
[t.start() for t in threads]
print('等待5s...')
time.sleep(5)
print('喚醒所有執行緒...')
event.set()
執行一下,看看結果
Thread: 1 start at Sun May 13 20:38:08 2018
Thread: 2 start at Sun May 13 20:38:08 2018
Thread: 3 start at Sun May 13 20:38:08 2018
Thread: 4 start at Sun May 13 20:38:08 2018
等待5s...
喚醒所有執行緒...
Thread: 1 finish at Sun May 13 20:38:13 2018
Thread: 4 finish at Sun May 13 20:38:13 2018
Thread: 2 finish at Sun May 13 20:38:13 2018
Thread: 3 finish at Sun May 13 20:38:13 2018
可見在所有執行緒都啟動(start()
)後,並不會執行完,而是都在self.event.wait()
止住了,需要我們通過event.set()
來給所有執行緒傳送執行指令才能往下執行。
2.4.2 Condition
Condition和Event 是類似的,並沒有多大區別。
同樣,Condition也只需要掌握幾個函式即可。
cond = threading.Condition()
# 類似lock.acquire()
cond.acquire()
# 類似lock.release()
cond.release()
# 等待指定觸發,同時會釋放對鎖的獲取,直到被notify才重新佔有瑣。
cond.wait()
# 傳送指定,觸發執行
cond.notify()
舉個網上一個比較趣的捉迷藏的例子來看看
import threading, time
class Hider(threading.Thread):
def __init__(self, cond, name):
super(Hider, self).__init__()
self.cond = cond
self.name = name
def run(self):
time.sleep(1) #確保先執行Seeker中的方法
self.cond.acquire()
print(self.name + ': 我已經把眼睛蒙上了')
self.cond.notify()
self.cond.wait()
print(self.name + ': 我找到你了哦 ~_~')
self.cond.notify()
self.cond.release()
print(self.name + ': 我贏了')
class Seeker(threading.Thread):
def __init__(self, cond, name):
super(Seeker, self).__init__()
self.cond = cond
self.name = name
def run(self):
self.cond.acquire()
self.cond.wait()
print(self.name + ': 我已經藏好了,你快來找我吧')
self.cond.notify()
self.cond.wait()
self.cond.release()
print(self.name + ': 被你找到了,哎~~~')
cond = threading.Condition()
seeker = Seeker(cond, 'seeker')
hider = Hider(cond, 'hider')
seeker.run()
hider.run()
通過cond來通訊,阻塞自己,並使對方執行。從而,達到有順序的執行。 看下結果
hider: 我已經把眼睛蒙上了
seeker: 我已經藏好了,你快來找我吧
hider: 我找到你了 ~_~
hider: 我贏了
seeker: 被你找到了,哎~~~
2.4.3 Queue佇列
最後一個,佇列,它是本節的重點,因為它是我們日常開發中最使用頻率最高的。
從一個執行緒向另一個執行緒傳送資料最安全的方式可能就是使用 queue 庫中的佇列了。建立一個被多個執行緒共享的 Queue 物件,這些執行緒通過使用put()
和 get()
操作來向佇列中傳送和獲取元素。
同樣,對於Queue,我們也只需要掌握幾個函式即可。
from queue import Queue
# maxsize預設為0,不受限
# 一旦>0,而訊息數又達到限制,q.put()也將阻塞
q = Queue(maxsize=0)
# 預設阻塞程式,等待佇列訊息,可設定超時時間
q.get(block=True, timeout=None)
# 傳送訊息:預設會阻塞程式至佇列中有空閒位置放入資料
q.put(item, block=True, timeout=None)
# 等待所有的訊息都被消費完
q.join()
# 通知佇列任務處理已經完成,當所有任務都處理完成時,join() 阻塞將會解除
q.task_done()
以下三個方法,知道就好,一般不需要使用
# 查詢當前佇列的訊息個數
q.qsize()
# 佇列訊息是否都被消費完,返回 True/False
q.empty()
# 檢測佇列裡訊息是否已滿
q.full()
函式會比之前的多一些,同時也從另一方面說明了其功能更加豐富。
我來舉個老師點名的例子。
# coding=utf-8
# /usr/bin/env python
'''
Author: wangbm
Email: wongbingming@163.com
Wechat: mrbensonwon
Blog: python-online.cn
公眾號:Python程式設計時光
date: 2020/9/20 下午7:30
desc:
'''
__author__ = 'wangbm'
from queue import Queue
from threading import Thread
import time
class Student:
def __init__(self, name):
self.name = name
def speak(self):
print("{}:到!".format(self.name))
class Teacher:
def __init__(self, queue):
super().__init__()
self.queue=queue
def call(self, student_name):
if student_name == "exit":
print("點名結束,開始上課..")
else:
print("老師:{}來了沒?".format(student_name))
# 傳送訊息,要點誰的名
self.queue.put(student_name)
class CallManager(Thread):
def __init__(self, queue):
super().__init__()
self.students = {}
self.queue = queue
def put(self, student):
self.students.setdefault(student.name, student)
def run(self):
while True:
# 阻塞程式,時刻監聽老師,接收訊息
student_name = queue.get()
if student_name == "exit":
break
elif student_name in self.students:
self.students[student_name].speak()
else:
print("老師,我們班,沒有 {} 這個人".format(student_name))
queue = Queue()
teacher = Teacher(queue=queue)
s1 = Student(name="小明")
s2 = Student(name="小亮")
cm = CallManager(queue)
cm.put(s1)
cm.put(s2)
cm.start()
print('開始點名~')
teacher.call('小明')
time.sleep(1)
teacher.call('小亮')
time.sleep(1)
teacher.call("exit")
執行結果如下
開始點名~
老師:小明來了沒?
小明:到!
老師:小亮來了沒?
小亮:到!
點名結束,開始上課..
其實 queue 還有一個很重要的方法,Queue.task_done()
如果不明白它的原理,我們在寫程式,就很有可能卡死。
當我們使用 Queue.get() 從佇列取出資料後,這個資料有沒有被正常消費,是很重要的。
如果資料沒有被正常消費,那麼Queue會認為這個任務還在執行中,此時你使用 Queue.join() 會一直阻塞,即使此時你的佇列裡已經沒有訊息了。
那麼如何解決這種一直阻塞的問題呢?
就是在我們正常消費完資料後,記得呼叫一下 Queue.task_done(),說明佇列這個任務已經結束了。
當佇列內部的任務計數器歸於零時,呼叫 Queue.join() 就不會再阻塞了。
要理解這個過程,請參考 http://python.iswbm.com/en/latest/c02/c02_06.html 裡自定義執行緒池的的例子。
2.4.4 總結一下
學習了以上三種通訊方法,我們很容易就能發現Event
和 Condition
是threading模組原生提供的模組,原理簡單,功能單一,它能傳送 True
和 False
的指令,所以只能適用於某些簡單的場景中。
而Queue
則是比較高階的模組,它可能傳送任何型別的訊息,包括字串、字典等。其內部實現其實也引用了Condition
模組(譬如put
和get
函式的阻塞),正是其對Condition
進行了功能擴充套件,所以功能更加豐富,更能滿足實際應用。
相關文章
- Java併發程式設計(04):執行緒間通訊,等待/通知機制Java程式設計執行緒
- Java併發程式設計之執行緒安全、執行緒通訊Java程式設計執行緒
- python 之 併發程式設計(守護程式、互斥鎖、IPC通訊機制)Python程式設計
- java併發程式設計 | 執行緒詳解Java程式設計執行緒
- Python併發程式設計之執行緒中的資訊隔離(五)Python程式設計執行緒
- Python併發程式設計之談談執行緒中的“鎖機制”(三)Python程式設計執行緒
- Java 併發程式設計 | 執行緒池詳解Java程式設計執行緒
- Java併發程式設計序列之JUC中Condition執行緒通訊Java程式設計執行緒
- python併發程式設計之多執行緒理論部分Python程式設計執行緒
- 阿里Android開發規範:程式、執行緒與訊息通訊阿里Android執行緒
- 執行緒間通訊_等待/通知機制執行緒
- 好程式設計師Python培訓分享Python中程式和執行緒詳解程式設計師Python執行緒
- Java中的執行緒通訊詳解Java執行緒
- Java 執行緒間通訊 —— 等待 / 通知機制Java執行緒
- flutter: 執行緒通訊與訊息迴圈Flutter執行緒
- 併發程式設計之多執行緒執行緒安全程式設計執行緒
- python 多執行緒程式設計Python執行緒程式設計
- Python多執行緒程式設計Python執行緒程式設計
- 《Java 多執行緒程式設計核心技術》筆記——第3章 執行緒間通訊(四)Java執行緒程式設計筆記
- 併發程式設計之:執行緒程式設計執行緒
- python基礎執行緒-管理併發執行緒Python執行緒
- Python程式和執行緒例項詳解Python執行緒
- 一起分析執行緒的狀態及執行緒通訊機制執行緒
- 高併發程式設計系列:4種Java執行緒鎖全面詳解程式設計Java執行緒
- Python併發程式設計之建立多執行緒的幾種方法(二)Python程式設計執行緒
- python多執行緒中訊息佇列如何實現?Python執行緒佇列
- 從執行緒到併發程式設計執行緒程式設計
- 併發程式設計與執行緒安全程式設計執行緒
- Java併發程式設計:Java執行緒Java程式設計執行緒
- java併發程式設計——執行緒池Java程式設計執行緒
- java併發程式設計——執行緒同步Java程式設計執行緒
- Java併發程式設計序列之執行緒間通訊-synchronized關鍵字-volatile關鍵字Java程式設計執行緒synchronized
- Java併發程式設計中的鎖機制詳解Java程式設計
- Python並行程式設計(二):多執行緒鎖機制利用Lock與RLock實現執行緒同步Python並行行程程式設計執行緒
- 【JAVA併發第三篇】執行緒間通訊Java執行緒
- Android 訊息機制詳解(Android P)Android
- 走進Java Android 的執行緒世界(三)Hander訊息機制JavaAndroid執行緒
- Python系統程式設計之執行緒Python程式設計執行緒