线程(Thread):想象线程就像是公司的员工。每个员工都有自己的任务和责任,但他们共享公司的资源(例如办公室、打印机等)。员工(线程)的上下班(开始和结束线程)以及工作调度(线程切换)由公司管理层(操作系统)控制,想想就挺残忍的。
如果公司要新增一个员工或者安排员工之间的工作,这需要管理层的直接参与,也会涉及到较多的人力和物力资源(也就是说,线程的创建和上下文切换成本相对较高)。
协程(Coroutine):现在想象协程就像是在家工作的自由职业者。他们使用自己的电脑和办公设备(拥有自己的堆栈和局部变量),并且自己决定什么时候工作、什么时候休息(编程者控制)。
他们可以随时暂停工作去喝杯咖啡或是散步(yield或等待),然后再回来继续工作。所有这些活动的安排都不需要外部管理层的参与(用户级的调度),并且几乎不需要额外的资源(低成本的任务切换)。
协作式调度:当前线程完全占用CPU时间,除非自己让出时间片,直到运行结束,系统才执行下一个线程。可能出现一个线程一直占有CPU,而其他线程等待。
抢占式调度:操作系统决定下一个占用CPU时间的是哪一个线程,定期的中断当前正在执行的线程,任何一个线程都不能独占。不会因为一个线程而影响整个进程的执行。
协程(Coroutines)的完整定义是“协作式调度的用户态线程”
协作式调度
A用6分钟, 我先用你一会用
B用4分钟
A是6分+B4分=10分
抢占式调度:AB都用打印机
A用2分钟
B用2分钟
A用2分钟
B用2分钟
A用2分钟
A是6分+B4分=10分
假设调度使用相同的时间,但是协程只需要2次调度切换而抢占式调用需要5次调度切换。
A在携程调度下6分钟完成就可以干别的了,但是普通均分调度下要等4分钟+工作6分钟=整体耗时10分钟来完成。
操作系统切换线程上下文的步骤如下所示:
1)保留用户态现场(上下文、寄存器、用户栈等)
2)复制用户态参数,用户栈切到内核栈,进入内核态
3)代码安全检查(内核不信任用户态代码)
4)执行内核态代码
5)复制内核态代码执行结果,回到用户态
6)恢复用户态现场(上下文、寄存器、用户栈等)
操作系统对线程有感知,经常管理,这就是需要耗费性能开销,
但是协程管不着,开销就没那么大,协程在开销上是轻量级的线程,不用处处系统感知管理。
协程不是操作系统的底层特性,系统感知不到它的存在。
它运行在线程里面,通过分时复用线程的方式运行,不会增加线程的数量。
协程也有上下文切换,但是不会切换到内核态去,比线程切换的开销要小很多。
在IO密集型的任务中有着大量的阻塞等待过程,协程采用协作式调度,在IO阻塞的时候让出CPU,当IO就绪后再主动占用CPU,牺牲任务执行的公平性换取吞吐量。
协程和线程的共同点:
都是并发操作,多线程同一时间点只能有一个线程在执行,协程同一时间点只能有一个任务在执行;
协程和线程的不同点:
多线程,是在I/O阻塞时通过切换线程来达到并发的效果,在什么情况下做线程切换是由操作系统来决定的,会造成竞争条件。
协程是用函数切换,开销极小,不通过操作系统调度,没有进程、线程的切换开销。
协程对比线程,在2个方面有影响:
在调度方式上(线程调度采用的调用cpu的方法在调度方式上(线程调度采用的调用cpu的方法)开销更小,
在上下文切换用户态上比线程更自由开销更小。
更简单:
协程用户态协作式调度方式比线程抢占式调度更优。
协程上下文切换比线程上下文系统切换更小。