协程 vs 线程

协程概念

  “协程”(Coroutine)概念最早由 Melvin Conway 于 1958 年提出。虽然被提出的时间很早,但是使用它的年限很短。尤其是最近几年,随着 Go、Lua 等语言的流行,把协程推向了一个新的高潮。

  协程其实可以认为是比线程更小的执行单元。为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机,我们可以把一个协程 切换到 另一个协程。只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

  协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。总的来说,协程为协同任务提供了一种运行时抽象,这种抽象非常适合于协同多任务调度和数据流处理。在现代操作系统和编程语言中,因为用户态线程切换代价比内核态线程小,协程成为了一种轻量级的多任务模型。

  从编程角度上看,协程的思想本质上就是控制流的主动让出(yield)和恢复(resume)机制,迭代器常被用来实现协程,所以大部分的语言实现的协程中都有 yield 关键字,比如 Python、Lua。但也有特殊比如 Go 就使用的是通道来通信。先来张图看看:

协程和线程差异

  那么这个过程看起来比线程差不多哇。其实不然 线程切换从系统层面远不止 保存和恢复 CPU上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

协程的问题

  但是协程有一个问题,就是系统并不感知,所以操作系统不会帮你做切换。那么谁来帮你做切换?让需要执行的协程更多的获得CPU时间才是问题的关键。

协程目前主流设计

  目前的协程框架一般都是设计成 1:N 模式。所谓 1:N 就是一个线程作为一个容器里面放置多个协程。那么谁来适时的切换这些协程?答案是有协程自己主动让出CPU,也就是每个协程池里面有一个调度器,这个调度器是被动调度的。意思就是他不会主动调度。而且当一个协程发现自己执行不下去了(比如异步等待网络的数据回来,但是当前还没有数据到),这个时候就可以由这个协程通知调度器,这个时候执行到调度器的代码,调度器根据事先设计好的调度算法找到当前最需要CPU的协程。切换这个协程的CPU上下文把CPU的运行权交个这个协程,直到这个协程出现执行不下去需要等等的情况,或者它调用主动让出CPU的API之类,触发下一次调度。对的没错就是类似于 领导人模式

这个设计有问题吗?

  其实是有问题的,假设这个线程中有一个协程是CPU密集型的且没有IO操作,也就是自己不会主动触发调度器调度的过程,那么就会出现其他协程得不到执行的情况,所以这种情况下需要程序员自己避免。这是一个问题,假设业务开发的人员并不懂这个原理的话就可能会出现问题。

协程举例理解

  在所有语言中都存在着层级调用,比如 A 调用 B,B 在执行过程中又调用了 C,C 执行完毕返回,B 执行完毕返回,最后是 A 执行完毕。这种方法、函数、子程序的调用方式都是通过 栈 实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

public void coroutineA() {
    System.out.println("1");
    System.out.println("2");
    System.out.println("3");
}

public void coroutineB() {
    System.out.println("a");
    System.out.println("b");
    System.out.println("c");
}

上面代码如果由协程来执行,那么在执行 coroutineA 的过程中,可以随时中断,去执行 coroutineBcoroutineB 也可能在执行过程中中断再去执行 coroutineA,所以,最终的结果可能是:

1
a
b
2
c
3

 但是,在上面的代码中,并没有在 coroutineA 方法中调用 coroutineB。执行结果就像两个线程在并发执行。但其实,通过协程执行用的是一个线程,只不过这个线程看起来有点“到处乱跑”。

协程优势

协程 vs 线程 比较有以下 3 个重要的优势:

  1、减少了线程切换的成本。线程,不管是创建还是切换,都需要较高的成本。子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。这也就是说,协程的效率比较高

  2、协程的第二大优势就是没有并发问题,不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多

  3、协程更轻量级。创建一个线程栈大概需要 1M 左右,而协程栈大概只需要几 K 或者几十 K。

  有优势也有劣势,因为前面的程序看起来在“上串下跳”,所以,协程看起来没那么好控制。

协程认知注意

  1、对于操作系统来说只有进程和线程,协程的控制由应用程序显式调度,非抢占式的

  2、协程的执行最终靠的还是线程,应用程序来调度协程选择合适的线程来获取执行权

  3、协程适合于 IO 密集型场景,这样能提高并发性,比如请求接口、Mysql、Redis 等的操作

  4、协程并不是说替换异步,协程一样可以利用异步实现高并发。

  5、协程要利用多核优势就需要比如通过调度器来实现多协程在多线程上运行,这时也就具有了并行的特性。如果多协程运行在单线程或单进程上也就只能说具有并发特性。

原文地址:https://www.cnblogs.com/liang1101/p/12777831.html