最早出现的是进程,后来为了调度的方便出现了线程,现在又蹦出了一个协程。这到底是个什么东西呢。
并发和并行:
最早的计算机,每次只能执行一个程序,别的都得等着。到后来,计算机运算速度提高了,于是就想要同一时间执行那么三五个程序,几个程序能一块跑一跑。特别是UI什么的,别跑个程序得排队等着。于是就有了并发。
从程序员的角度可以看成是多个独立的逻辑流。把单cpu时间分片,能快速的切换逻辑流,看起来像是大家一块跑的。这个时候内存其实是共享的。后来一电脑上有了好几个cpu,好咧,大家都别闲着,可以一块跑。就是所谓的并行。
进程:
但是一块跑也有问题。我的程序计算到一半,刚把多次方程解到最后一步,你突然插进来,我的中间状态咋办,我用来储存的内存被你覆盖了咋办?所以跑在一个cpu里面的并发都需要处理上下文切换的问题。进程就是这样抽象出来个一个概念,搭配虚拟内存、进程表之类的东西,用来管理独立的程序运行、切换。
因为程序的使用涉及大量的计算机资源配置,把这活随意的交给用户程序,容易让整个系统被搞挂,资源分配也很难做到相对的公平。所以就出现了操作系统,核心的操作需要陷入内核(kernel),切换到操作系统,让内核来做。
进程的切换需要做以下操作:
- 切换页全局目录(Page Global Directory)来加载一个新的地址空间,实际上就是加载新进程的cr3寄存器值。
- 切换内核堆栈和硬件上下文,这些包含了内核执行一个新进程的所有信息,包含了CPU寄存器。
线程:
有的时候碰着I/O访问,阻塞了后面所有的计算。空着也是空着,内核就直接把CPU切换到其他进程,让人家先用着。当然除了I\O阻塞,还有时钟阻塞等等。
一开始大家都这样弄,后来发现不成,太慢了。为啥呀,一切换进程得反复进入内核,置换掉一大堆状态。进程数一高,大部分系统资源就被进程切换给吃掉了。
后来搞出线程的概念,大致意思就是,这个地方阻塞了,但我还有其他地方的逻辑流可以计算,这些逻辑流是共享一个地址空间的,不用特别麻烦的重新加载地址空间,页表缓冲区,只要把寄存器刷新一遍就行,能比切换进程开销少点。
进程和线程切换时的对比:
上下文内容 | 进 程 | 线 程 |
指向可执行文件的指针 | x | |
栈 | x | x |
内存(数据段和堆) | x | |
状态 | x | x |
优先级 | x | x |
程序I/O的状态 | x | |
授予权限 | x | |
调度信息 | x | |
审计信息 | x | |
有关资源的信息 ● 文件描述符 ● 读/写指针 |
x | |
有关事件和信号的信息 | x | |
寄存器组 ● 栈指针 ● 指令计数器 ● 诸如此类 |
x | x |
协程:
如果连时钟阻塞、 线程切换这些功能我们都不需要了,自己在进程里面写一个逻辑流调度的东西。那么我们即可以利用到并发优势,又可以避免反复系统调用,还有进程切换造成的开销,分分钟给你上几千个逻辑流不费力。这就是用户态线程。
从上面可以看到,实现一个用户态线程有两个必须要处理的问题:
- 一是碰着阻塞式I\O会导致整个进程被挂起;
- 二是由于缺乏时钟阻塞,进程需要自己拥有调度线程的能力。
如果一种实现使得每个线程需要自己通过调用某个方法,主动交出控制权。那么我们就称这种用户态线程是协作式的,即是协程。协程的做法很像早期操作系统的协作式多任务。
协作式多任务:
当任务得一个到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU。win3.x就是这个方式。
但是,对于操作系统来说,这种做法会让系统不稳定。因为操作系统管理者整个计算机的资源,这个做法容易让系统失去控制(比如用户程序的一个死循环),因此,现在的操作系统都是用的是抢占式多任务。而在一个程序内,使用协作式的方法是可行的,因为自己的程序可以自己控制。
可以这么理解:协程就是在用户程序中实现了协作式任务调度。
总结:
三种调度的技术虽然有相似的地方,但并不冲突。
进程调度可以很好的控制资源分配,线程调度让进程内部不因某个操作阻塞而整体阻塞。协程则是在用户态来优化程序,让程序员以写同步代码的方式写出异步代码般的效率。
转载请注明:旅途@KryptosX » 进程,线程,协程