跳到主要内容

协程

为什么会出现协程

进程和线程都属于系统级的任务,切换进程、线程都需要经历用户态跃迁内核态,切换成功后再由内核态切回用户态。

线程被挂起,如果调用是在主线程,UI不就阻塞了么?不过协程并不会阻塞线程。

一般的耗时任务都不会消耗 CPU,比如磁盘、网络 IO 等,这时候如果让线程干等着,就会浪费 CPU 资源。我们能不能让线程先去干其它事情,等异步任务完成后告诉它,然后再回去继续执行原来的任务呢?

协程解决了这个问题。它支持当前任务 CPU 的执行权限,并在需要的时候又恢复权限,这就需要保存切换前的状态。

协程通过在线程中实现调度,避免了陷入内核级别的上下文切换造成的性能损失,进而突破了线程在IO上的性能瓶颈。

协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

协程,是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

协程与子程序(函数)

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和普通的子程序不同。

协程在子程序内部是可中断的,然后转而执行别的子程序,在适当的时候再返回来接着执行,可以把其称谓一种特殊的函数(子程序)。

协程既不是进程也不是线程,协程仅仅可以看做是一个特殊的函数,协程它和进程不是一个维度的,是一种可以暂停执行过程的函数,它可以中断当前的执行过程直到下一个 Yield 指令达成。

协程和线程的关系

协程是属于线程的。协程程序是在线程里面跑的,因此协程又称微线程和纤程等

协程与线程比较

1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。

2) 线程进程都是同步机制,而协程则是异步

3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

4) 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。

5) 协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程.。

为什么协程比线程切换消耗小

(1)协程切换完全在用户空间进行,线程切换涉及特权模式切换,需要在内核空间完成; (2)协程切换相比线程切换做的事情更少。

协程切换只涉及基本的CPU上下文切换,所谓的 CPU 上下文,就是一堆寄存器,里面保存了 CPU运行任务所需要的信息。

协程切换非常简单,就是把当前协程的 CPU 寄存器状态保存起来,然后将需要切换进来的协程的 CPU 寄存器状态加载的 CPU 寄存器上就 ok 了。而且完全在用户态进行,一般来说一次协程上下文切换最多就是几十ns 这个量级。

协程的优点

  • 协程的切换开销小,属于程序级别的切换,操作系统完全感知不到,因而更轻量级,单线程内就可以实现并发的效果,最大限度地利用cpu
  • 极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;
  • 不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源

协程的缺点

  • 协程的本质是单线程下,无法利用多核,可以是一个程序开多个进程,每个进程内开多个线程,每个线程内开启协程;协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程;
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序:这一点和事件驱动一样,可以使用异步IO操作来解决

协程的应用

在JavaScript的ES6里Generator就是用协程来调度的它的yield就是中断

参考