go语言协程底层io异步 golang 异步io

协程与函数线程异步的关系什么协程
协程这个概念在计算机科学里算是一个老概念了 , 随着现代计算机语言与多核心处理器的普及,似乎也有普及之势 。协程是与例程相对而言的 。
熟悉C/C++语言的人都知道,一个例程也就是一个函数 。当我们调用一个函数时,执行流程进入函数;当函数执行完成后,执行流程返回给上层函数或例程 。期间,每个函数执行共享一个线程栈;函数返回后栈顶的内容自动回收 。这就是例程的特点,也是现代操作系统都支持这种例程方式 。
协程与例程相对,从抽象的角度来说,例程只能进入一次并返回一次 , 而协程可能进入多次并返回多次 。比如说,我们有下面一段程序:
void fun(int val)
{
int a=0; //1
int b=0; //2
int c=a+b; //3
}
如果上面的代码是一个例程,那么它只能把 1、2、3 依次执行后,才返回 。如果是协程,它可能在 1 处暂停,然后在某个时刻从 2 处继续执行;接着在 2 处执行完之后暂停,然后在另外一个时刻从 3 处继续执行 。
从抽象角度 , 协程就这么简单 。
异步IO的特点与分析
在了解协程的特点(可以多次进入同一个函数,并接着上次运行处继续执行)后,我们再来考虑一下,这一特点如何应用到异步IO程序中 。在异步IO程序中,有很大一块代码是处理异步回调的,也就是数据读取或写入由系统执行 , 当任务完成后,系统会执行用户的回调 。如果只是很少使用这种回调,那么程序并不会因为异步而复杂多少 , 但要是程序中异步回调大量存在,那么此时我们会发现 , 原本简单的程序可能因为回调而变得支离破碎,原本一个简单的循环,现在需要写入多个函数,并在多个函数里来回调用 。下面示例一下:
//下面代码片断是同步代码 , 它从IO读一段数据,并把这段数据写回
void start()
{
for(;;)
{
Buffer buf;
read (buf);//把书读到buf
write(buf);//把buf的数据写回
}
//注意到没有,同步代码很简单直接 , 一个循环,几行代码完成全部事务
}
//把上面的同步代码映射为异步,代码量可能要增加很多 , 并且程序逻辑也变得不清晰
//示例如下
//读回调 , 在回调里我们发起写操作
void readHandle(buf)
{
writeAsync(buf, writeHandle);
}
//写回调,在回调里我们发起读操作
void writeHandle(buf)
{
readAsync(buf, readHandle);
}
//开始循环
void start()
{
static Buffer buf; //buf变量不能在栈上,为了简单这里写成静态变量
readAsync(buf, readHandle);
}
从上面的代码比较中 , 我们可以看出异步IO会把代码分隔成许多碎片 , 同时原本清晰的处理逻辑也因为被放入多个函数里 , 而变得很不清晰 。上面的同步代码,一个了解程序的初级程序员也可以读懂写出,但相同功能的异步代码,一个初级程序员可能就搞不定了,甚至很难搞明白为什么要这么做 。
读到这里,对异步不是太了解的人可能会问,既然异步把问题搞复杂了,那我们为什么还要用异步呢?答案简单有力,为了“性能” 。只有这一个原因 , 当程序需要处理大量IO时,异步的效率将高出同步代码许多倍 。如何一个程序的性能不其关心部分,那真不应该使用异步IO 。
对比我们的异步IO代码与其功能相同的同步代码,我们发现每个异步调用都是要把代码分隔一个小函数——比原本要小的函数,当异步调用返回后,我们又接着下面处理 。这一点跟协程很像,在一个协程里 , 当发起异步IO时 , 我让它返回,当异步IO完成后,我让这个协程接着执行,处理余下的逻辑 。

推荐阅读