Python GIL锁(施工中)
1/对于一组cpu密集型任务(即假设了他们都在几乎100%用cpu无IO阻塞),总运行时间就是 每个任务的cpu执行时间求和,没有其他的。【要降低总时间,必须给更多的cpu核心,所有任务并行跑,至于选择多进程或多线程,都可以,区别是多线程开销更小多进程更安全,本来应该选多线程。但是python特殊点,由于gil, 所以多核心多线程近似等价于多核心单线程,还有额外的线程调度颠簸的开销。所以python选多进程】
【但是从用户角度】
即使是单核使用多进程,不能减少总cpu执行时间,但是时间片轮转能让所有任务都能得到调度,用户体验较好(因为如果是单线程,那下一个任务就只能等待上一个任务全部完成才能开始,)
如果是多核,每个核上跑一个任务,如果假设这些任务之间几乎是独立的(就是很少有竞争共享变量这种交互),那么就是纯粹意义上的并行。所以大家建议 设置进程数小于等于核心数,避免进程大于核心数 需要做cpu切换进程调度开销。
2/对于一组io密集型任务,每个任务的运行时间是 cpu计算时间+io时间(io时完全不需要cpu, 它就是纯粹等待数据传输直至完成,所以将它挂起剥夺cpu它还是在io等待不影响它io)。
所以一个最简单的改进方案是,让A任务的io时间和B任务的cpu时间重叠,即A任务做io等待时让出cpu给B任务做cpu计算,等B任务io等待时让出cpu给C任务做cpu计算,诸如此类。并发任务数越多,时间重叠越多,纯粹io空等的时间浪费就被减少了。
如果用是单核 多进程或多线程,都可以。但是多线程的线程切换开销更小,以及多进程内存开销大,所以更考虑多线程。(gil? 无所谓,本来每个时刻就只能运行一个线程,如果A等待,就不仅让出cpu也让出gil, 让B开始运行)。这里的本质还是减少了io空等时间,让这部分空等时间给了其他进程运行。
如果是多核的多进程或多线程。多进程,其实是OK的,因为一个核上一个进程,互不影响。但是多进程的内存成本高啊,一个进程一个内存空间,而速度和单核的多线程又是一样的。。。所以不选多进程。如果选多线程。但是,由于gil影响,所有核心同时只能运行一个线程。。。这就把每个核上单独运行一个线程 变成 所有核上只能运行一个线程。没有线程级别的多核并行了。而且还有多核调度gil的颠簸(cpu0刚放出gil,其他核也去抢但是肯定没有cpu0成功率高,结果其他核心上的线程开始抢又没抢到,线程状态颠簸,性能开销 )。
综上,
cpu密集型,多核的多进程并行(不是并发);io密集型,单核的多线程并发(不是并行,其他语言其实应该选多核的多线程并行加并发,但python应该选单核并发)
几个关键知识点
(1)减少cpu运行时间只能是多核并行跑多个子任务
(2)gil全局锁影响的是并行而不是并发。它会导致多核上面的多线程并行 等价于 多线程并发,以及额外的线程颠簸,所以python多核选多进程
(3)cpu密集型,唯一的办法是需要用多核并行来减小总cpu计算时间,因此多核多进程并行。
(4)io密集型,最合理的办法是让多任务并发,让A任务io阻塞时间和B任务cpu计算时间重叠而不是串行先后,从而【单个任务总运行时间没有减少,但是时间重叠,导致多个任务总运行时间减少了,画张overlap图一目了然】。因此选择单核多线程。(因为线程相比进程,内存占用少 ,切换开销也小。这个时候本来就是并发的,所以gil不影响单核多线程的并发)
3/画个222表格可能更直观。8种情况
cpu密集型,io密集型
单核,多核
多进程,多线程
但是python是同一时刻,一个核心也只能运行一个线程,因为gil全局锁
还有一点忘了提。
为啥Python要用gil全局锁?
答: 控制全局共享变量的访问。记住 锁一定为了控制互斥变量的访问(一般指的是写互斥变量)
再提一点。
多核上用多进程并行,提高io密集型理论上是OK的,但是和 单核上多线程并发 没有性能优势,反而多进程多核并行用了很多资源(多核,进程对内存开销很大)。
所以 多进程多核并行 不是解决 io密集型任务的首选方案,但确实可选。
再提一点。
这种cs基础,一定先讲通理论,以及普适于各种编程语言的情况,再具体到python有啥特殊性。这才是正确的触类旁通。
References
https://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p09_dealing_with_gil_stop_worring_about_it.html
https://zhuanlan.zhihu.com/p/75780308
https://zhuanlan.zhihu.com/p/20953544