浅谈阻塞以及协程
本文最后更新于:2022年12月19日 晚上
序、提出的问题
在正式开始之前,我想先提出几个问题,然后一起带着问题以及思考去阅读这篇文章
1、什么是协程
2、协程的作用是什么
一、阻塞与非阻塞
首先我们来回顾一些基础的概念,对于操作系统来说,什么是阻塞和非阻塞呢?
阻塞和非阻塞的区别在于:是否立即返回数据,在非阻塞的模型中,应用发起请求(比如IO请求后),系统会立即返回,程序可以再次发起请求,直到系统处理完毕,返回数据。
这里写了一个小的demo,实现非阻塞IO读取本地文件,可以从代码中看出,在读取文件并且未读取完成的时候,系统会返回EAGAIN,线程不断地轮询,直到系统读取完成并返回。
1 |
|
二、多路复用
即使是非阻塞模型,应用的每一个请求,依然需要创建一个线程去处理,这显然也不是一种很优雅的解决方法。如何用少的线程管理多的请求呢,答案是多路复用。
在多路复用的场景下,系统将请求的可写可读操作分离了出来,使用单独的线程进行管理。举个例子,系统可以创建3个线程处理6个请求,当6个请求进入的时候,会经过多路复用器,多路复用器会阻塞请求线程,同时轮询是否有空闲线程,如果有空闲线程的话就分配并进行读写操作。
虽然线程在读写时依然是阻塞的,但是配合非阻塞的IO操作,依然可以实现高并发下的网络连接,这就是reactor模型。
三、异步
不难发现即使是多路复用,依然有阻塞的产生,异步的概念随之而来。与异步相关的另一个名词:回调,可以很好地解释异步这个概念。异步在发起请求后不会阻塞,而是会继续处理其他请求,当系统IO处理完成后,会通知程序处理返回。程序不用阻塞、反复地轮询是否ready,可以直接切换到其他任务,等待回调即可。
不仅是异步,在上述的多路复用模型中,都有等待数据返回这一个操作,当多个事件都需要回调时,代码会变得非常复杂。在Java中的Netty框架封装了基于nio(同步非阻塞)的多路复用,让IO的调用变得快速且简单,同时它也有另外一种解决方法:协程。
四、什么是协程
A coroutine is a function that can suspend its execution (yield) until the given given YieldInstruction finishes.
首先想要说明,在我个人的理解里,协程更多的是一种技术的概念,而非一种技术的实现,协程没有一个统一的定义,通俗地讲:协程就是一个基于用户态的轻量的线程,它可以实现线程的一些功能,但又对kernel不可见,调度都由程序控制。
如果熟悉操作系统的话,我们可以知道,系统对线程的调度是抢占式的,即线程的资源控制权利在系统,不在用户。协程便可以实现协作式的调度,即用户有权利控制自己占有多久协程。
ps:协作式的调度会出现一个协程占用过多的时间,不交出资源的情况,golang在1.14引入了抢占式协程调度,而kotlin对协程的实现依然是协作式的,由此来看,协程更多的只是一个概念,每个人,每个语言,都可以用不同的方式去实现,js等语言的async await的实现我认为也可以算协程,即使它是无栈协程。有栈和无栈的协程相关知识不在这里展开,感兴趣的话可以查看文章:有栈协程与无栈协程。
因此协程的开启以及切换的开销和线程对比起来,都小了很多。用线程的话可能我们最多只能开启几千个线程,但是协程我们可以做到几万甚至更多。
提到协程,我们一瞬间基本上都能想到以下几个优点
- 节省内存
- 节省线程创建时syscall的开销
- 协程的切换开销很小
- 可以配合非阻塞IO,提高系统性能
五、协程的作用是什么
上文讨论了很多IO以及协程相关的问题,现在我们对看到协程都会形容它:轻量级的线程。对于我们程序员来说,这个轻量级的线程最大的作用是什么:提高程序速度?毕竟它比线程轻,能开几十上百万的协程。但协程最大的作用是:对异步以及回调的封装,让程序员可以用写同步代码的方法写异步IO。我们可以看一段golang的代码:
1 |
|
这段代码发起了一个网络IO请求,得到结果并输出,这是一个我们再熟悉不过的同步代码书写,但这其实是一个异步/回调的执行,在发起http请求后,go创建了一个新的协程去处理网络请求,并让出当前线程的资源去进行其他任务,当请求完成后再通过线程调度协程恢复,继续进行后续的处理。
让我们回到文章的开头,思考阻塞与非阻塞。当时提到:对操作系统来说,讲到这应该都清楚一件事,那就是线程是内核可以感知的,而协程是内核无法感知的,所以协程的”阻塞”,对于操作系统以及线程而言,都是”非阻塞”的状态,对于golang来说,当一个协程进行IO操作的时候,golang会在用户态阻塞这个协程,内核态是无感的,golang将这个协程yield出去,将资源分配给其他的go协程。这里的重点也是:协程的切换代价比线程的切换低了很多。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!