1 线程池原理
主线程是通过队列将任务传递给多个子线程。一旦主线程将任务塞进任务队列,子线程们就会开始争抢,最终只有一个线程能抢到这个任务,并立即进行执行,执行完后将结果放进Future对象就完成了这个任务的完整执行过程。
主线程将任务塞进线程池后得到了这个Future对象,它内部的_result还是空的。如果主线程调用result()方法获取结果,就会阻塞在条件变量上。如果子线程计算任务完成了就会立即调用set_result()方法将结果填充进future对象,并唤醒阻塞在条件变量上的线程,也就是主线程。这时主线程立即醒过来并正常返回结果。
2 进程池原理
主线程将任务塞进TaskQueue(普通内存队列),拿到Future对象
唯一的管理线程从TaskQueue获取任务,塞进CallQueue(分布式跨进程队列)
子进程从CallQueue中争抢任务进行处理
子进程将处理结果塞进ResultQueue(分布式跨进程队列)
管理线程从ResultQueue中获取结果,塞进Future对象
主线程从Future对象中拿到结果
3 跨进程队列
进程池模型中的跨进程队列是用multiprocessing.Queue实现的。
它使用无名套接字sockerpair来完成的跨进程通信,socketpair和socket的区别就在于socketpair不需要端口,不需要走网络协议栈,通过内核的套接字读写缓冲区直接进行跨进程通信。
4 I/O密集型与CPU密集型任务的选择
concurrent提供了两种并发模型,一个是多线程ThreadPoolExecutor,一个是多进程ProcessPoolExecutor。对于IO密集型任务宜使用多线程模型。对于计算密集型任务应该使用多进程模型。
是因为Python GIL的存在让Python虚拟机在进行运算时无法有效利用多核心。对于纯计算任务,它永远最多只能榨干单个CPU核心。如果要突破这个瓶颈,就必须fork出多个子进程来分担计算任务。而对于IO密集型任务,CPU使用率往往是极低的。
参考:
https://juejin.im/post/5b1e36476fb9a01e4a6e02e4