Python 现代开发学习笔记

(三)线程池、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 等待设超时,不会把底层阻塞调用停掉。

待运行任务被取消,运行任务等待超时后仍继续,最终收到释放信号

笔记正文与全部题解 · Ctrl / ⌘ K