进程、线程与协程概念理解
一、进程
进程是一个具有一定独立功能的程序的一次动态执行过程,是操作系统进程资源分配的基本单位,通常包含三部分
- 程序
- 数据
- 程序控制块PCB:包含进程描述信息和控制信息
二、线程
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度的基本单位。
用户线程与内核线程
一对一模型
一个用户线程就唯一地对应一个内核线程,一个用户线程唯一地映射到一个物理CPU的内核线程。
优点:
- 一个线程因某种原因阻塞时其他线程的执行不受影响
- 让多线程程序在多处理器的系统上有更好的表现
缺点:
- 用户线程的数量受到限制
- 内核线程调度时,上下文切换的开销较大,导致用户线程的执行效率下降
多对一模型
将多个用户线程映射到一个内核线程上,线程之间的切换由用户态的代码来进行。
缺点:
- 如果其中一个用户线程阻塞,那么其它所有线程都将无法执行,因为此时内核线程也随之阻塞
- 在多处理器系统上,处理器数量的增加对多对一模型的线程性能不会有明显的增加,因为所有的用户线程都映射到一个处理器上
多对多模型
多对多模型结合了一对一模型和多对一模型的优点,将多个用户线程映射到多个内核线程上,由线程库负责在可用的可调度实体上调度用户线程,这使得线程的上下文切换非常快,因为它避免了系统调用。现流行的操作系统大多都采用多对多模型。
优点:
- 一个用户线程的阻塞不会导致所有线程的阻塞,因为此时还有别的内核线程被调度来执行;
- 多对多模型对用户线程的数量没有限制;
- 在多处理器的操作系统中,多对多模型的线程也能得到一定的性能提升,但提升的幅度不如一对一模型的高。
协程
协程(Coroutines)是一种基于线程之上,但又比线程更加轻量,由程序员自己来管理的轻量级线程(用户空间线程)。
因为是自主开辟的异步任务,所以很多人把它们称为纤程(Fiber)。
为什么需要协程
背景:在传统的J2EE系统中都是基于每个请求占用一个线程去完成完整的业务逻辑,系统的吞吐能力取决于每个线程的操作耗时。如果遇到很耗时的I/O行为,则整个系统的吞吐立刻下降,因为这个时候线程一直处于阻塞状态,如果线程很多的时候,会存在很多线程处于空闲状态(等待该线程执行完才能执行)。
例子说明:JDBC是同步阻塞的,这也是说数据库是性能瓶颈的原因。这里的耗时其实是让CPU一直在等待I/O,线程没有使用CPU去做运算,而是处于空转状态。而过多的线程,也会带来更多的上下文切换开销。
对于上述问题,现阶段行业里的比较流行的解决方案之一就是单线程加上异步回调。
而协程的目的则是当出现长时间的I/O操作时,通过让出目前的协程调度,执行下一个任务的方式,来消除上下文切换开销。
协程的特点
- 线程切换由操作系统调度,协程由用户调度,因此减少了上下文切换,提高了效率
- 线程的默认栈大小是1M,而协程更轻量,接近1K。因此可以在相同的内存中开启更多的协程
- 由于在同一个线程上,因此可以避免竞争关系而使用锁
- 适用于被阻塞的,且需要大量并发的场景。但不适用于大量计算的多线程,遇到此种情况,更好是用线程去解决。
协程的原理
当出现IO阻塞的时候,由协程的调度器进行调度,通过将数据流yield掉(主动让出),并且记录当前栈上的数据,阻塞完后立刻再通过线程恢复栈,并把阻塞的结果放到这个线程上去跑,这样看上去好像跟写同步代码没有任何差别,这整个流程可以称为coroutine,而跑在由coroutine负责调度的线程称为纤程。
由于协程的暂停完全由程序控制,发生在用户态上;而线程的阻塞状态是由操作系统内核来进行切换,发生在内核态上。因此,协程的开销远远小于线程的开销。