Coroutine, 异步,同步,async, await

Coroutine, 异步,同步,async, await

引言

为什么把这么多词汇放到同一个标题里面?

因为它们合起来可以实现一种装逼技能,那就是:借助于由async, await构成的coroutine, 用同步的方式,编写异步的代码!

异步的代码用同步的方式来写?有什么用啊?请看下面分解——

异步的代码是什么?

首先要解决的问题是什么叫同步和异步。同步的意思是什么?举个例子,我们看视频的时候,如果画面和声音不同步,会觉得很别扭。所以同步可以理解为多个对象具有相对应的关系。异步我们生活中用得很少,它一般是在专业领域出现,比如异步IO,异步通信,英文是asynchronous。简单理解就是不是同步。(<--这不是废话嘛。)本文指编程里面的异步方式,如异步代码,异步IO。且看下面的例子——

比如我们函数AWeek里面要做的事情是打印周一,播放音乐,打印周二,刷个朋友圈,打印周三。同步的代码的样子:

function AWeek() {
  // 同步的代码,同步的方式
  print('Monday')
  play_music() // block call
  print('Tuesday')
  browse_moment() // block call
  print('Wednesday')
}



playmusic() 和browse_moment()是阻塞调用,程序会等着这些事情完成然后再继续执行接下来的步骤。

而异步代码长什么样子呢?以JavaScipt 为例子。

在没有async,await的久远年代,代码是怎么写的呢?它们写得像下面这个样子——

function AWeek() {
  print("Monday")
  Playing_Music(
    () => {
        print('Tuesday');
        ((cb) => {
            Browsing_Moment(cb);
        })(() => { print("Wednesday") });
    }
 ) 
}

可以看到, 代码由各种callback串起来,晦涩难懂,维护起来困难(对于大神除外)。ES6有了Promise,代码可以写成下面的方式

function AWeek() {
  print("Monday")
  Playing_Music_Promise().then(() => {
    print('Tuesday');
    Browsing_Moment_Promise().then(() => {
        print('Wednesday');
    })
 })
}

接着ES7有了async, await,代码就可以很方便写成

async function AWeek() {
  // 异步的代码,同步的方式编写
  print('Monday')
  await Playing_Music_Promise();
  print('Tuesday')
  await Browsing_Moment_Promise();
  print('Wednesday');
}

跟同步的代码长得几乎一模一样,容易理解和编写!没有所谓的callback一个插一个。它们长得既然相似,但是执行起来确实异步的。具体见下面分析。

Coroutine 协程

异步的代码 虽然用同步的方式写了,但是在执行时候,它们是像下面的方框那样执行的,中间加入许多方框,做了好多事情。

所以异步的代码指的是仍按照一定的序列执行,但是中间穿插了其他的方框,即,做事情仍按照顺序,但是并不是时间上相连的顺序,而是逻辑上的顺序。它们很适用于IO密集的应用,比如Nginx,NodeJS。

带有aysn, await, yield, promise的函数就是coroutine, 那么什么是coroutine呢?

Coroutine就是函数,只不过是可以suspend和resume的函数,也就是你可以暂停这个函数的执行(实际上就是在suspend的地方直接返回到caller了),去做其他事情,然后在恰当的时候恢复到你离开的位置继续开始运行。

如果对上图还不是很理解,那么我们代入线程后看看是怎么实现的。

如下图左边灰色的coroutine(一个特别点的函数),当它被调用的时候,被切分成了两个部分,线程1在执行完成第一部分后,线程1就继续做其他事情了。第二部分被suspend了。当时机出现的时候,线程2(可以是线程1)就开始执行第二部分。

Coroutine in C++ 20

这一章才是我写这篇文章的原因——讲讲C++ 20的coroutine。

C#(2012),JavaScript(2015)早早就有了coroutine(async, await,promise, etc.)而C++居然直到2020年才有,截止此刻,可以在C++ compiler support查看看各个主流编译器的支持状态。

在C#,JavaScript等程序员在用coroutine用得飞起的时候,C++程序员还没看到苗头。

等C++程序员看到了苗头以后,却发现C++的coroutine怎么不像JavaScript/C#那么好用。

这里的系列文章讲得很详细,我是没有在第一遍读完的时候看懂。如果你看懂了,就不用看下面的内容了。

废话少说,其实Coroutine就是函数,只不过是可以suspend和resume的函数,也就是你可以暂停这个函数的执行(返回给caller),去做其他事情,然后在恰当的时候恢复到你离开的位置继续开始。

比如上面异步的代码的框框们,在做正经事的时候,我们插入了很多灰色的(框框)事情。做完这些灰色的事情后,我们又继续执行计划执行的事情,伪代码如下,

//异步代码的执行顺序
async function AWeek() {
  // 异步的代码,同步的方式编写
  print('Monday')
  计划 Playing_Music_Promise();
  修了个bug。
  真正的Playing_Music
  print('Tuesday')
  计划 Browsing_Moment_Promise();
  看了篇文章
  又看了篇文章
  真正的Browsing_Moment_Promise
  print('Wednesday');
}

由于C++的Coroutine需要花的篇幅比较多,我就放在新的文章:Coroutine in C++ 20 ~感兴趣可以的移步继续阅读~

附注

Coroutine包含了await, yield, async关键词的函数。在JavaScript里面一般不叫coroutine,叫async call或者Promise。

C++ 对应有co_await,co_yield, 还多了个co_return。async对应什么呢?请看我的续篇The Coroutine in C++ 20 协程浅析


参考文献:

  1. C++ Coroutines: Understanding operator co_await
  2. Awaiting

编辑于 2021-10-01 09:50