Python - Futures
并行 vs 并发
- 并发(Concurrency) - 在某个特定的时刻,只允许有一个操作发生,线程和任务之间会相互切换,交替运行
- 并行(Parallelism) - 在同一时刻,有多个操作同时进行
- Python 中有两种并发形式 - threading + asyncio
threading
- 操作系统知道每个线程的所有信息,在适当的时候做线程切换
- 优点 - 代码易于编写,程序员不需要做任何切换操作
- 缺点 - 容易出现 race condition
asyncio
- 主程序想要切换任务时,必须得到此任务可以切换的通知
- 避免了 race condition 的情况
场景
- 并发通常用于 IO 密集的场景 - Web 应用
- 并行通常用于 CPU 密集的场景 - MapReduce
线程池 vs 进程池
大部分时间是浪费在 IO 等待上
多线程(并发) - 16.8s -> 3.5s
1 | with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: |
- 创建一个线程池,总共 5 个线程
- 线程数并非越多越好 - 线程的创建、维护和删除都会存在一定的开销
executor.map
- 与内置的map
类似,表示对 sites 中的每一个元素,并发地调用函数
多线程(并行) - 3.5s -> 2.1s
1 | with concurrent.futures.ProcessPoolExecutor() as executor: |
- 创建进程池,使用多个进程并行地执行程序
- 一般省略 max_workers 参数,默认为 CPU 数
Futures
- Futures 模块,位于 concurrent.futures 和 asyncio 中,表示带有延迟的操作
- Futures 会将处于等待状态的操作包裹起来放到队列中
- 这些操作的状态可以随时查询,操作的结果或异常,可以在操作完成后获取
- 一般不需要去考虑如何创建 Futures,而是考虑怎么调度这些 Futures 的执行
- 当执行
executor.submit(func)
时,会安排里面的 func 函数执行,并返回创建好的 Future 实例
- 当执行
Method | Desc |
---|---|
done() | Return True if the future is done - non-blocking |
add_done_callback(fn) | Add a callback to be run when the future becomes done |
result() | Return the result this future represents |
as_completed | An iterator over the given futures that yields each as it completes |
executor.submit
将任务放进 future 队列,等待执行as_completed
在 future 完成后输出结果- future 列表中每个 future 完成的顺序,与在列表中的顺序并一定完全一致
- 取决于系统调度和每个 future 的执行时间 - 有一定的随机性
GIL
在同一时刻,Python 主程序只允许有一个线程执行
Python 的并发,是通过切换多线程完成的
GIL - 全局解释器锁
- Python 的解释器并不是线程安全的
- 为了解决由此带来的 race condition 问题,Python 便引入了全局解释器锁
- 在同一时刻,只允许一个线程执行
- 在执行 IO 操作时,如果一个线程被阻塞了,则 GIL 也会被释放,从而让另一个线程能够继续执行
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.