threading 多执行绪处理
Python 在执行时,通常是采用同步的任务处理模式 ( 一个处理完成后才会接下去处理第二个 ),然而 Python 的标准函数“threading”采用“执行绪”的方式,运用多个执行绪,在同一时间内处理多个任务 ( 异步 ),这篇教学会介绍 threading 的用法。
快速导览:
本篇使用的 Python 版本为 3.7.12,所有范例可使用 Google Colab 实作,不用安装任何软件 ( 参考:使用 Google Colab )
同步与异步
同步和异步的常见说法是:“同步模式下,每个任务必须按照顺序执行,后面的任务必须等待前面的任务执行完成,在异步模式下,后面的任务不用等前面的,各自执行各自的任务”,也可以想像成“同一个步道 vs 不同步道”,透过步道的方式,会更容易明白同步和异步。( 因为有时会将同步与异步的中文字面意思,想成“一起走”或“不要一起走”,很容易搞错 )
- 同步:“同一个步道”,只能依序排队前进。
- 异步:“不 ( 非 ) 同步道”,可以各走各的。
import threading
要使用 threading 必须先 import threading 函数库。
更多信息可以参考 Python 官方文件:threading
import threading
有没有使用 threading 的差异
在没有使用 threading 的状况下,如果不同的函数里都有“循环”,则循环的执行会按照函数执行的顺序进行,以下方的程序码为例,执行后会先印出 aa() 函数的循环内容,执行完毕再印出 bb() 函数的循环内容。
import time
def aa():
i = 0
while i<5:
i = i + 1
time.sleep(0.5)
print('A:', i)
def bb():
i = 0
while i<50:
i = i + 10
time.sleep(0.5)
print('B:', i)
aa() # 先執行 aa()
bb() # aa() 結束後才會執行 bb()
'''
A: 1
A: 2
A: 3
A: 4
A: 5
B: 10
B: 20
B: 30
B: 40
B: 50
'''
如果有使用 threading,则两个函数就可以同时运作 ( 虽然是同时,但底层仍然是有几毫秒的执行顺序 )
import threading
import time
def aa():
i = 0
while i<5:
i = i + 1
time.sleep(0.5)
print('A:', i)
def bb():
i = 0
while i<50:
i = i + 10
time.sleep(0.5)
print('B:', i)
a = threading.Thread(target=aa) # 建立新的執行緒
b = threading.Thread(target=bb) # 建立新的執行緒
a.start() # 啟用執行緒
b.start() # 啟用執行緒
'''
A: 1
B: 10
A: 2
B: 20
A: 3
B: 30
A: 4
B: 40
A: 5
'''
threading 基本用法
建立 threading 的方法如下:
thread = threading.Thread(target=function, args)
# function 要在執行緒裡執行的函式
# args 函式所需的引數,使用 tuple 格式,如果只有一個參數,格式 (參數,)
建立 threading 之后,就可以使用下列常用的方法:
| 方法 | 说明 |
|---|---|
| start() | 启用执行绪。 |
| join() | 等待执行绪,直到该执行绪完成才会进行后续动作。 |
| ident | 取得该执行绪的标识符。 |
| native_id | 取得该执行绪的 id。 |
| is_alive() | 执行绪是否启用,启用 True,否则 False。 |
下方的程序码执行后,会将 aa()、bb() 和 cc() 三个函数分别建立为执行绪,接着当使用 a.start() 和 b.start() 方法启用后,因为有加入 a.join() 和 b.join() 的等待方法,所以 c.start() 会在 aa() 与 bb() 执行完成后,才会启用。
import threading
import time
def aa():
i = 0
while i<5:
i = i + 1
time.sleep(0.5)
print('A:', i)
def bb():
i = 0
while i<50:
i = i + 10
time.sleep(0.5)
print('B:', i)
def cc():
i = 0
while i<500:
i = i + 100
time.sleep(0.5)
print('C:', i)
a = threading.Thread(target=aa)
b = threading.Thread(target=bb)
c = threading.Thread(target=cc)
a.start()
b.start()
a.join() # 加入等待 aa() 完成的方法
b.join() # 加入等待 bb() 完成的方法
c.start() # 當 aa() 與 bb() 都完成後,才會開始執行 cc()
'''
A: 1
B: 10
A: 2
B: 20
A: 3
B: 30
A: 4
B: 40
A: 5
B: 50
C: 100 <--- A B 都結束後才開始
C: 200
C: 300
C: 400
C: 500
'''
如果只加入 a.join() 而不加入 b.join(),则 cc() 会在 aa() 执行结束就开始。
import time
def aa():
i = 0
while i<5:
i = i + 1
time.sleep(0.5)
print('A:', i)
def bb():
i = 0
while i<100:
i = i + 10
time.sleep(0.5)
print('B:', i)
def cc():
i = 0
while i<500:
i = i + 100
time.sleep(0.5)
print('C:', i)
a = threading.Thread(target=aa)
b = threading.Thread(target=bb)
c = threading.Thread(target=cc)
a.start()
b.start()
a.join() # 加入等待 aa() 完成的方法
c.start() # 當 aa() 完成後,就會開始執行 cc()
'''
A: 1
B: 10
A: 2
B: 20
A: 3
B: 30
A: 4
B: 40
A: 5
B: 50
C: 100 <--- A 結束就開始
B: 60
C: 200
B: 70
C: 300
B: 80
C: 400
B: 90
C: 500
B: 100
'''
Lock() 锁定
使用 threading 建立执行绪后,可以使用 Lock() 方法建立一个执行绪的“锁”,当 Lock 建立后,就能使用 acquire() 方法锁定,使用 release() 方法解除锁定,如果有多个执行绪共用同一个 Lock,则同一时间只会执行第一个锁定的执行绪,其他的执行绪要等到解锁才能够执行。
以下方的程序码为例,会先使用 threading.Lock() 建立一个 Lock,当 aa() 和 bb() 执行时会先使用 lock.acquire() 进行锁定 ( 以执行的顺序表示谁先锁定 ),因为 aa() 比较先执行所以会先锁定,接着当 aa() 里的 i 等于 2 时使用 lock.release() 解除锁定,这时 bb() 就可以开始执行。
import threading
import time
def aa():
lock.acquire() # 鎖定
i = 0
while i<5:
i = i + 1
time.sleep(0.5)
print('A:', i)
if i==2:
lock.release() # i 等於 2 時解除鎖定
def bb():
lock.acquire() # 鎖定
i = 0
while i<50:
i = i + 10
time.sleep(0.5)
print('B:', i)
lock.release()
lock = threading.Lock() # 建立 Lock
a = threading.Thread(target=aa)
b = threading.Thread(target=bb)
a.start()
b.start()
'''
A: 1
A: 2
B: 10
A: 3
B: 20
A: 4
B: 30
A: 5
B: 40
B: 50
'''
Event() 事件处理
threading 除了基本的多执行续功能,也提供 Event() 事件处理的方法,透过“事件”的方式,就能让不同的执行续之间彼此沟通,也能轻松做到“等待 A 执行绪完成某件事后,B 执行绪再继续”的功能,事件的处理包含下方几种方法:
| 方法 | 说明 |
|---|---|
| threading.Event() | 注册一个事件。 |
| set() | 触发事件。 |
| wait() | 等待事件被触发。 |
| clear() | 清除事件触发,事件回到未被触发的状态。 |
下方的程序码执行后,会注册一个 event 事件,当 aa() 执行时使用 event.wait() 等待事件被触发,接着设定 bb() 执行到 i 等于 30 的时候就会触发事件,这时 aa() 才会开始运作。
import threading
import time
def aa():
event.wait() # 等待事件被觸發
event.clear() # 觸發後將事件回歸原本狀態
for i in range(1,6):
print('A:',i)
time.sleep(0.5)
def bb():
for i in range(10,60,10):
if i == 30:
event.set() # 觸發事件
print('B:',i)
time.sleep(0.5)
event = threading.Event() # 註冊事件
a = threading.Thread(target=aa)
b = threading.Thread(target=bb)
a.start()
b.start()
'''
B: 10
B: 20
B: 30
A: 1
B: 40
A: 2
B: 50
A: 3
A: 4
A: 5
'''
下方的程序码注册了两个事件,event_a 会在输入任意内容后触发,触发后就会印出 1~5 的数字,印出完成后会触发 event_b,这时才又可以继续输入文字,不断重复两个事件的触发与执行续的执行。
import threading
import time
def aa():
i = 0
while True:
event_a.wait() # 等待 event_a 被觸發
event_a.clear() # 還原 event_a 狀態
for i in range(1,6):
print(i)
time.sleep(0.5)
event_b.set() # 觸發 event_b
def bb():
while True:
input('輸入任意內容')
event_a.set() # 觸發 event_a
event_b.wait() # 等待 event_b 被觸發
event_b.clear() # 還原 event_b 狀態
event_a = threading.Event() # 註冊 event_a
event_b = threading.Event() # 註冊 event_b
a = threading.Thread(target=aa)
b = threading.Thread(target=bb)
a.start()
b.start()
'''
輸入任意內容a
1
2
3
4
5
輸入任意內容b
1
2
3
4
5
輸入任意內容
'''
小结
Python 的 threading 内建函数库是一个相当方便的函数库,不仅可以让原本同步的执行变成异步,大幅减少工作时间,其中的 Event() 方法更是非常好用,相信对于异步程序的撰写,能有十足的帮助。
微信扫码关注
抖音扫码关注