协程
线程分为用户级线程,内核级线程和轻量级线程。Linux中使用的是轻量级线程,而协程虽然是运行在线程之上,但是是run在用户空间。并且协程和线程一样,拥有自己的调度器、cpu的上下文切换等。
协程在我个人看来是一种用户级线程;
- 这是因为对于cpu有上下文的切换,而且是在用户空间的层次进行数据处理;一旦被内核的代码阻塞,是无法进行解除阻塞状态;
协程的功能
首先需要明确一点,协程是干嘛的,针对的事物是什么?
- 协程针对的是IO处理,可以将多个同步关系的IO处理的性能接近于异步IO的效率;
- 这样既保证了编写代码的逻辑,又保证了代码执行的效率;
异步IO的效率高,是正常的,因为调用某个IO函数,也就是系统调用,根据异步IO模型中的描述,它不会被阻塞且处理完毕后会通知调用线程,也就是异步IO模型没有阻塞时间。
协程的做法
那么问题就来了,应该是一个什么样的逻辑处理同步IO,才能让其性能接近异步IO呢?
- 消除同步IO被阻塞住时的等待时间,从而让效率无限接近于异步IO。
- 这就是协程干的功能,一旦被阻塞住了,就会把cpu让出给其他可以执行的协程。
cpu切换上下文过程
协程由运行体和调度器组成。
协程的运行体中保存着让出后的寄存器状态,方便于之后恢复、子过程函数和其参数、自身协程的状态、栈的大小等;
也就引出了yeild和resume这两个功能
- yeild的中文名叫让出,cpu每个时刻只能运行一个操作(),让出操作会让当前cpu的寄存器空出给其他协程运行体(函数)使用
- resume的中文名叫恢复,cpu空出后,恢复之前协程运行体(函数)执行的位置。
由于讨论的是一个线程的操作,不会出现内核切换线程的操作。
接下来要说是调度器,使用yeild和resume两个操作,进行切换协程。使用协程A->调度器->协程B这种形式的切换,而不是协程A->协程B。下图可以很好说明这个形式。
协程状态检测
协程调度器在调度协程运行体的时候就需要维护协程所具有的状态,比如就绪、等待、睡眠。
这也就需要一些数据结构进行维护这些运行体。
- 就绪状态由于这些运行体不需要设置优先级,就可以使用队列先进先出的性质;
- 睡眠一定会有过期时间,就可以使用定时器相关的数据结构,比如红黑树、最小堆等。
- 等待IO准备也是有时间的,同睡眠状态一样,使用相同的数据结构
总结
- 协程是针对同步IO处理的一个组件,让同步逻辑的代码有着异步的效率;
- 利用IO阻塞的时间,去处理其他的事情。但是你要说它是异步也不对,它的整体代码逻辑是串行的;
- 协程是由运行体和调度器组成;
- 运行在一个线程上,针对于服务器开发,不需要为每一个客户端连接的IO创建一个线程(这种方法本身就是不对的,因为有上万个客户端,不能也创建上万个线程,对吧),让这个度比使用线程更小。
扩展
至于它和reactor进行对比,它俩的性能差距不会很大。
协程相比于reactor,reactor的回调函数太过于分散,不易代码可读。
reactor其实是对事件进行一个异步的处理,于此同时也不需要看到IO处理的逻辑,只需要关注每一个事件应该怎么做。