实际工作中,如果某个函数用到了全局状态的变量,频繁读写的时候,并发调用的时候就容易出现各种bug。因此在这种情境下就要使用队列将并发的请求依次执行。
博主直接使用ThreadPoolExecutor(max_workers=1),它的内部就是 一条 FIFO 队列 + 一个固定工作线程, 不需要手写 queue.Queue:
py
pool = ThreadPoolExecutor(max_workers=1)
# 入队 + 等结果
result = pool.submit(unsafe_func, arg).result()
示例代码
这里博主写了一份代码直观的说明这个使用方法:
其中第一段代码中由于是并发的执行unsafe,所以除了最后一个线程,其他线程在等待的时候,STATE就已经被赋值成了最后一个线程的序号=9,因此其他所有线程在最后返回时,检查
STATE == n都会是False;而第二段代码通过ThreadPoolExecutor(max_workers=1).submit()就可以依次将请求压入队列,相当于单线程执行,因此每次STATE == n都会是True
py
import sys, time
from concurrent.futures import ThreadPoolExecutor
sys.stdout.reconfigure(encoding="utf-8")
STATE = 0
def unsafe(n):
"""这是一个简单的模拟处理函数"""
global STATE
t0 = time.time()
STATE = n
time.sleep(0.05)
return n, STATE == n, t0, time.time()
def run(label, fn):
with ThreadPoolExecutor(max_workers=10) as p:
r = list(p.map(fn, range(10))) # 得到执行的结果
base = min(x[2] for x in r)
print(f"=== {label} ===")
for n, ok, s, e in r:
print(f" {n:2d}: {s - base:5.3f}->{e - base:5.3f} {'OK' if ok else 'FAIL'}")
print(f" 成功 {sum(x[1] for x in r)}/10\n")
run("直接并发", unsafe)
serial = ThreadPoolExecutor(max_workers=1)
run("串行化后", lambda n: serial.submit(unsafe, n).result())
运行后的结果是:
text
=== 直接并发 ===
0: 0.000->0.067 FAIL
1: 0.000->0.067 FAIL
2: 0.001->0.067 FAIL
3: 0.001->0.065 FAIL
4: 0.001->0.065 FAIL
5: 0.002->0.065 FAIL
6: 0.002->0.065 FAIL
7: 0.002->0.065 FAIL
8: 0.002->0.065 FAIL
9: 0.003->0.065 OK
成功 1/10
=== 串行化后 ===
0: 0.000->0.057 OK
1: 0.057->0.119 OK
2: 0.119->0.181 OK
3: 0.181->0.243 OK
4: 0.243->0.304 OK
5: 0.304->0.365 OK
6: 0.365->0.427 OK
7: 0.427->0.490 OK
8: 0.490->0.553 OK
9: 0.553->0.614 OK
成功 10/10