文章目录
知识补充
想要学习多进程与多线程,首先要理解以下概念:
1. 并发与并行
一个cpu同时执行多个线程的过程叫做并发;虽然叫做并发,但在底层CPU每次只能执行一个线程,
具体方法是将cpu分成多个时间片段,然后让CPU以这些时间片段为最小单位轮流执行这些线程
多个cpu同时执行多个线程的过程叫做并行,并行才是真正的同时执行多个任务,如果主机只有一个CPU,那么就无法实现并行过程
说的通俗一些,并行与并发只是和cpu的使用数量相关;并发技术目的是增加单个CPU的运行效率,而并行技术的目的是通过增加CPU的核心数量进而增加整体的计算效率
2. CPU密集型与I/O密集型
2.1 CPU密集型(CPU bound)
CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。
2.2 IO密集型(I/O bound)
IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
正文
1. 多进程与多线程的区别
- 一个进程内可以包含多个线程,一个线程只能属于一个进程
- 一个进程内所有线程共享同一片内存空间、共享一个cpu资源
- 多个进程之间的内存空间相互独立,相互之间不能够共享数据(多个进程可以通过某种方式用实现数据共享,但是成本较高)
- 根据2,3可知,进程是分配存储资源的基本单位,线程是分配CPU资源的基本单位
2. 多线程、多进程、CPU核心的关系
一般情况下,多线程主要有两种,一种是用户态多线程;一种是内核态多线程,两者的主要区别是能否支持多核下并行运行
- 同一进程下的用户态多线程,只能在同一个CPU内核上进行计算
- 同一进程下的内核态多线程(java1.2之后用内核级线程),在操作系统内核的支持下可以在多核下并行运行
这里要单独解释一下Python的多线程和多进程:
由于 Python GIL(全局解释锁)的存在,使得 Python 同一个时刻只有一个线程在一个CPU内核上执行字节码,无法将多个线程映射到多个CPU内核上,即不能发挥多核CPU的优势;
换句话说,Python同一进程下的多个线程只能并发执行,不能并行执行,因此,Python多线程技术对于IO密集型任务的效率提升明显,但对于CPU密集型任务 Python多线程技术的效果较差
3. 多进程与多线程如何选择
3.1 一般情况
对于一般编程语言来说,线程和进程各自的优势如下:
对比维度 | 多进程 | 多线程 | 总结 |
---|---|---|---|
数据共享、同步 | 数据共享复杂,需要用IPC;数据是分开的,同步简单 | 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 | 各有优势 |
内存、CPU | 占用内存多,切换复杂,CPU利用率低 | 占用内存少,切换简单,CPU利用率高 | 线程占优 |
创建销毁、切换 | 创建销毁、切换复杂,速度慢 | 创建销毁、切换简单,速度很快 | 线程占优 |
编程、调试 | 编程简单,调试简单 | 编程复杂,调试复杂 | 进程占优 |
可靠性 | 进程间不会互相影响 | 一个线程挂掉将导致整个进程挂掉 | 进程占优 |
分布式 | 适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单 | 适应于多核分布式 | 进程占优 |
3.2 Python语言下的选择
由于 Python GIL锁的存在,在Python中,对于同一进程中的多个线程并不能在多个CPU内核上并行执行,因此除了上述因素,在Python中还要考虑要处理的任务是否是CPU密集型任务
如果是CPU密集型任务,那么对于每个任务来说,CPU都满载状态,这时就需要使用多进程技术,将多个任务分配到多个CPU内核上进行执行(多进程下每个进程都有一个单独的GIL,因此多个进程可以同时执行多个线程)
如果是IO密集型任务,CPU在很多时候属于闲置状态,那么就可以使用多线程技术给CPU分配更多的线程,从而增加CPU的使用效率,
补充:
在Python中,对于CPU密集型任务,CPU核数越多,多线程(单进程)的效率反而会更低;
为了解释这个原因,我们先要了解 Python进行线程切换的过程:
- Pyhton解释器释放GIL锁
- 调用操作系统的线程调度模块,唤醒 N 个(N=CPU核心数)线程
- 一个线程获得GIL锁并执行,其他线程转为待调度状态
过程2解读: 这里操作系统的线程调度模块和Python内部的GIL锁没有关系,需要分开来看;正常情况下操作系统认为多个CPU核心可以并行执行多个线程,因此当调用操作系统的线程调度模块时,操作系统会为每个CPU核心唤醒一个线程,等待被调用
过程3解读: 虽然过程2唤醒了N个线程等待被调用,但是由于Python GIL的存在,每次也只有一个线程能够获得GIL锁并执行;唤醒每个线程是要消耗资源的,核心越多每次GIL释放后系统唤醒的线程数量也会越多,Python中只有一个线程能够最终被执行,那么唤醒多个线程必然会浪费资源,这其实是Python多核多线程效率低的根本原因。
参考文章: