阅读这篇文章,你将会了解:
1.协程的概念
2.进程、线程、线程的区别
3.协程有什么好处,如何提升性能
一.概念:
- 协程是在线程下的,线程内的多个协程通过协程的调度获得cpu时间。
- 线程内的多个协程是串行的。即线程下的某个协程在运行时,其他协程必然是挂起,没有在运行。
- 一个进程可以有多个线程,一个线程可以用多个协程。
二.进程、线程、协程三者的上下文切换比较
进程 | 线程 | 协程 | |
---|---|---|---|
切换者 | 操作系统 | 操作系统 | 用户(编程者/应用程序) |
切换时机 | 根据操作系统自己的切换策略,用户不感知 | 根据操作系统自己的切换策略,用户不感知 | 用户自己的程序决定 |
切换内容 | 页全局目录,内核栈,硬件上下文 | 内核栈,硬件上下文 | 硬件上下文 |
切换内容的保存 | 保存于内核栈中 | 保存于内核栈中 | 保存在用户自己的变量(用户栈或堆) |
切换过程 | 用户态 - 内核态 - 用户态 | 用户态 - 内核态 - 用户态 | 用户态(没用进入内核态) |
切换效率 | 低 | 中 | 高 |
三.协程带来了什么好处
从编程风格的角度看:
很多人在理解协程的时候,都会觉得协程就像个语法糖。很多的时候就像是一个异步调用的实现,等一个“I/O阻塞”的方法完成后,再切回来恢复上下文继续完成接下来的逻辑。所以总结来说,这种风格的编程更易于理解,而且一样方便于代码的复用。
从性能的角度看:
1.在学习的过程中,让我最疑惑的一点是协程能否解决因“I/O阻塞”引起的性能问题?
总所周知,在我们实际的程序中遇到一些“I/O阻塞”的操作时,我们总是希望对应的线程能自觉的让出CPU时间片给其他线程,直到I/O完成后通过一些机制唤醒之前挂起的线程,这样能够提升CPU的利用率,提升程序的性能。
一个线程可以拥有多个协程,当协程运行过程中遇到了“I/O阻塞”的操作,如果协程无法让出时间片,将会导致其他的协程仍然无法运行,那么协程实际并没有解决“I/O阻塞”引起的性能问题。
所以,假若协程想解决这个问题,那么协程实现库必须是实现了一些底层的监听,例如发现当前协程即将要进行“网络I/O”时(假设当前操作系统的网络IO模型是epoll),协程库能劫持该系统调用,把这些IO操作注册到系统网络IO的epoll事件中(包括IO完成后的回调函数),注册完成则把当前协程挂起,让出cpu资源给其他协程。直到该IO操作完成,通过异步回调的方式通知该协程库去恢复之前挂起的协程,协程继续执行。
在实际场景中,不只是网络IO,硬盘的读写也会引起IO阻塞,包括我们常用的数据库读写、文件读写等。所以协程能否在这方面提升性能,要看对应协程库能否感知该操作,能否支持异步。
2.协程的同步写法却能拥有异步的性能?
这个问题实际上在上面已经得到解答了。虽然协程的使用是通过同步的写法实现的。如果希望得到性能上的提升,实际上还是通过异步回调的支持,只不过这个异步回调是底层模型上完成的。所以上协程上关于同步异步的性能是建立在底层的异步模型上的。
3.协程占用的空间可以比线程小
协程是用户定义的,所以在设计协程时,协程的最小空间占用可以比线程小很多。在一个服务器很难创建10W个线程,但是可以轻松的创建10W个协程。
4.协程切换比线程的切换更轻量
先控制一下变量,不考虑阻塞的问题
假设4核的cpu,100个任务并发执行
(1)创建100个线程:100个线程参与cpu调度。
(2)创建10个线程,每个线程创建10个协程:10个线程参与cpu调度,每个协程内的10个协程由协程库自己进行调度。
理论上,进程拥有100个线程时,每个线程的时间片会比拥有10个线程时短,也就意味着在相同时间里,拥有100个线程时的上下文切换次数比拥有10个线程时多。
所以协程并发模型与多线程同步模型相比,在一定条件下会减少线程切换次数,但是增加了协程切换次数,由于协程的切换是由协程库调度的,所以很难说协程切换的代价比省去的线程切换代价小,合理的方式应该是通过测试工具在具体的业务场景得出一个最好的平衡点。
四.参考文档:
https://www.cnblogs.com/laiy/p/coroutine.html
https://blog.csdn.net/chengqiuming/article/details/80573288