武汉加油~ 医护人员加油~ 今天看到确诊人数曲线和疑似人数曲线终于呈下降趋势,我也该收收心好好学习了!
并发与并行
无论是用什么语言编程,让代码变得简洁高效一直都是编写程序追求的目标,而高效肯定离不开并发与并行,看了很多解释,先整理记录一部分,后续有深入理解再进行补充。下面PO一个我很喜欢的解释:
并发:强调的是可以一起【出发】
与可以一起出发的并发(concurrent)相对的是不可以一起出发的顺序(sequential)
- 顺序:上一个开始执行的任务完成后,当前任务才能开始执行
- 并发:无论上一个开始执行的任务是否完成,当前任务都可以开始执行
并行:强调的是可以一起【执行】
与可以一起执行的并行(parallel)相对的是不可以一起执行的串行(serial)
- 串行:有一个任务执行单元,从物理上就只能一个任务、一个任务地执行
- 并行:有多个任务执行单元,从物理上就可以多个任务一起执行
概念出处:https://www.zhihu.com/question/33515481/answer/452128444
进程与线程
这一部分涉及到很多操作系统与底层硬件层面,尤其是底层硬件层面是我几乎完全不熟悉的,所以只作简单的梳理。
进程是操作系统进行资源分配(CPU时间片、内存等资源)和调度的一个独立单位,是资源分配的最小单位,是应用程序运行的载体。
线程有时被称为轻量级进程(LWP),是操作系统调度(CPU调度)执行的最小单位。
进程与线程的区别:
- 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源
- 处理机分给线程,即真正在处理机上运行的是线程
- 线程在执行过程中,需要协作同步,不同进程的线程需要利用消息通信的方法实现同步。
协程
协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。
- 协程不是进程也不是线程,而是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比并不是一个维度的概念。
- 一个线程可以包含多个协程,简单来说,一个线程内可以由多个这样的特殊函数在运行,但是需要注意的是,一个线程的多个协程的运行是串行的。如果是多核CPU,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内协程却绝对是串行的,无论CPU有多少个核。 当一个协程运行时,其他协程必须挂起。
![](https://i-blog.csdnimg.cn/blog_migrate/2b5ef29fc5c2c27c60db79229cfbd76f.png)
多进程与多线程
一般来说,一个CPU核心只能分配给一个进程,以便运行这个进程,如果计算机只有一个CPU核心,而需同时运行多个进程,就必须使用并发技术;如果计算机有多个CPU核心,当进程数小于CPU核心数时,不同的进程可以分配给不同的CPU核心来运行,这便是并行,如果进程数大于CPU核心数,则仍然需要使用并发技术。由于进行CPU分配是以线程为单位的,一个进程可能由多个线程组成,因此有如下关系:
- 总线程数 <= CPU核心数:并行运行
- 总线程数 > CPU核心数:并发运行
多进程与多线程的选择
对比维度 | 多进程 | 多线程 | 总结 |
---|---|---|---|
数据共享、同步 | 数据共享复杂,需要用IPC;数据是分开的,同步简单 | 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 | 各有优势 |
内存、CPU | 占用内存多,切换复杂,CPU利用率低 | 占用内存少,切换简单,CPU利用率高 | 线程占优 |
创建销毁、切换 | 创建销毁、切换复杂,速度慢 | 创建销毁、切换简单,速度很快 | 线程占优 |
编程、调试 | 编程简单,调试简单 | 编程复杂,调试复杂 | 进程占优 |
可靠性 | 进程间不会互相影响 | 一个线程挂掉将导致整个进程挂掉 | 进程占优 |
分布式 | 适用于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单 | 适用于多核分布式 | 进程占优 |
GIL(全局解释器锁)
Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。
GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
在调用任何Python C API之前,要先获得GIL
GIL缺点: 多处理器退化为单处理器;优点:避免大量的加锁解锁操作
GIL的早期设计
Python支持多线程,而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像MySQL这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,并且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?
GIL的影响
无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行。
所以,python是无法利用多核CPU实现多线程的。
这样,python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
参考
https://www.jianshu.com/p/6dde7f92951e
https://www.cnblogs.com/dengyongqing/articles/7497458.html
https://www.cnblogs.com/virusolf/p/5458325.html
https://www.cnblogs.com/yuanchenqi/articles/6755717.html