(三)线程池、Future 与完成次序
1. 让线程池复用工作线程
# 项目文件:demos/thread_pool.py
# submit 返回 Future;result 取得返回值,as_completed 按任务完成情况交出 Future。
from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep
def read_title(title, delay):
sleep(delay)
return title
if __name__ == "__main__":
items = [("Python", 0.06), ("NumPy", 0.02), ("Pygame", 0.04)]
with ThreadPoolExecutor(max_workers=3) as pool:
futures = [pool.submit(read_title, title, delay) for title, delay in items]
completed = [future.result() for future in as_completed(futures)]
ordered = [future.result() for future in futures]
print("完成后收集", completed)
print("按输入排列", ordered)submit(函数, 实参...) 把一次函数调用交给池,立即返回代表这次调用的 Future。任务可能正在等待、执行或已经结束;result() 需要时等待完成,取得返回值,任务抛错时则在取结果的位置重新抛出。
完成列表经常先出现 NumPy,但运行时不要依靠某个延迟值承诺完成次序。第二个列表始终按原来的 Future 列表读取,得到 Python、NumPy、Pygame;先等待 Python 的结果,并不会撤销另外两个已经提交的任务。
pool.map(函数, 数据) 也会并发调用,并按输入顺序交出结果。Python 3.12 的 Executor.map 会先收集迭代输入,很多任务时还要考虑待处理数量;需要逐项控制时,继续使用 submit 或任务队列。线程池与 Future
2. 等待超时与停止任务是两个动作
# 项目文件:demos/future_timeout.py
# Event 控制正在运行的任务何时结束,让取消与等待超时的结果可以重复观察。
from concurrent.futures import ThreadPoolExecutor
from threading import Event
def wait_for_release(started, release):
started.set()
release.wait()
return "已完成"
if __name__ == "__main__":
started, release = Event(), Event()
with ThreadPoolExecutor(max_workers=1) as pool:
running = pool.submit(wait_for_release, started, release)
started.wait()
pending = pool.submit(lambda: "尚未开始")
try:
print("取消等待项", pending.cancel())
print("取消运行项", running.cancel())
try:
running.result(timeout=0)
except TimeoutError:
print("本次等待超时,线程仍在等待释放")
finally:
release.set()
print(running.result())输出依次为 取消等待项 True、取消运行项 False、等待超时说明、已完成。Event.set() 发出信号,wait() 等待信号。Future.cancel() 可以取消尚未开始的任务,不能强行终止这个已经运行的线程。
离开线程池的 with 会等待已运行的任务完成,所以先在 finally 里释放事件。真实网络调用也应在调用本身配置超时;只给外面的 Future 等待设超时,不会把底层阻塞调用停掉。