进程, 线程, 协程
进程
进程一般由程序、数据集合和进程控制块三部分组成。
- 程序用于描述进程要完成的功能,是控制进程执行的指令集;
- 数据集合是程序在执行时所需要的数据和工作区;
- 程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。
进程是程序的实例,一个程序可以有多个实例,但一个实例只能对应一个进程
线程
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成
任务调度
大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式。
多线程与多核
多核(心)处理器是指在一个处理器上集成多个运算核心从而提高计算能力,也就是有多个真正并行计算的处理核心,每一个处理核心对应一个内核线程。
内核线程(Kernel Thread,KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。一般一个处理核心对应一个内核线程,比如单核处理器对应一个内核线程,双核处理器对应两个内核线程,四核处理器对应四个内核线程。
超线程技术就是利用特殊的硬件指令,把一个物理芯片模拟成两个逻辑处理核心
程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Lightweight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,也被叫做用户线程。由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。
线程切换
线程切换的实现一般放在内核,中断处理程序也是放在内核。从这个角度来说,如果线程当前处于用户态,若要发生线程切换,必然是要先要先进入内核态,发生状态切换的。线程切换的原因还可能是其他类型的中断,或者线程自身主动进入等待或者睡眠,这些情况无一例外都是要进入内核的。
线程的切换分两种情况: 同进程内, 和不同进程内切换;
线程模型
一对一模型: 一个用户线程对应一个内核线程
CON
- 许多操作系统限制了内核线程的数量,因此一对一模型会使用户线程的数量受到限制;
- 许多操作系统内核线程调度时,上下文切换的开销较大,导致用户线程的执行效率下降。
一对多模型: 多个用户线程对应一个内核线程
CON
- 如果其中一个用户线程阻塞,那么其它所有线程都将无法执行,因为此时内核线程也随之阻塞了;
- 在多处理器系统上,处理器数量的增加对多对一模型的线程性能不会有明显的增加
多对多模型: 多个用户线程对应一个内核线程
PRO
- 一个用户线程的阻塞不会导致所有线程的阻塞,因为此时还有别的内核线程被调度来执行;
- 多对多模型对用户线程的数量没有限制;
- 在多处理器的操作系统中,性能得到提升
协程
协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性
因为是自主开辟的异步任务,所以很多人也更喜欢叫它们纤程(Fiber),或者绿色线程(GreenThread)
为什么使用协程
- 由于现代CPU发展的越来越快,每一个时间片能够处理的指令越来越多,很经常导致一个线程时间片还未用完,而进入了等待数据IO的时间。此时该线程必然要让出CPU,让另一个线程运行
- 之后有人提出了协程的概念,让同一个线程在一个CPU时间片内可以处理多个任务,虽然协程的切换也耗费时间,但总体来说要比线程的切换要好很多。因此,使用协程可以比使用线程能够得到更好的处理效果(这个不是一定的,还得跟服务器的任务性质以及协程的调度算法有关系,但一般情况下使用协程会比使用线程有更好的性能)
协程解决什么
协程的目的就是当出现长时间的I/O操作时,通过让出目前的协程调度,执行下一个任务的方式,来消除ContextSwitch上的开销。
避免CPU一直等待IO, 增加延迟 同时可以避免创建过多的线程, 造成更多上下文切换
进程 vs 线程
线程与进程最大的区别在于,线程是调度的基本单位,而进程则是资源拥有的基本单位。
所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。
- 当进程只有一个线程时,可以认为进程就等于线程。
- 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。
- 另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的
线程的上下文切换分成两种:
- 前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样
- 前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据
协程 vs 线程
items | thread | goroutine |
---|---|---|
资源占用 | 8M | 2k |
调度所属 | OS | 用户 |
切换开销 | 涉及模式切换、16个寄存器 | PC/SP/DX |
性能 | 创建和销毁开销大 | 小 |
数据同步 | 依赖锁 | 只有一个线程,不存在同时写变量冲突,在协程中控制共享资源不加锁 |
寄存器
- PC 程序寄存器 - 程序指定地址
- SP 栈地址
- DX 数据寄存器