引言
如果你还在想办法理解协程 (coroutine)是什么,那么就让我们玩一玩分手厨房~
Overcooked,是一款多人烹饪游戏,玩家需要在特定的时间内做出尽可能多的菜。据说玩了的情侣会分手,所以又名分手厨房。你想不想要挑战一下(*^▽^*)
今天的主题是协程 (coroutine),与分手厨房有什么关系呢?协程 (coroutine),是现代编程里面少不了并且重要的概念。它不仅让你的代码逼格更高,也让你的程序在特定场景下性能更好!
很多人花了很多时间并不一定能理解它。而游戏,却很容易理解,所以本文借助分手厨房游戏来讲讲什么是协程。
Overcooked
先让我们来看看分手厨房的玩法——
如上图,我们一共有四个玩家,分别控制着带厨师帽的小人(没有厨师帽的是一些NPC)。
游戏开始后,左上角会不停地出现订单,而玩家们通过不停地完成这些订单得分。
每个订单都会标明需要什么材料,比如红色的代表西红柿,灰色的是蘑菇,等等。每个订单有自己的制作流程,比如先把西红柿切碎,然后拿去煮,接着盛到盘子里上菜。哒哒,明白这些规则就可以上手玩耍了~
做菜越快,完成的订单越多,积分也就越高。如何才能更快地更好地完成订单呢?需要小伙伴分工明确,密切地配合,马不停蹄地做菜。比如同学A负责切菜,拿食材,同学B负责煮,上菜等等。有时候能者还要干更多的活。
简单的规则,但是需要不简单的操作和配合。订单不停地出现,长时间不能完成的订单就会销毁,然后被扣分。长时间煮着的食物,如果不盛出来就会着火,着火就要去救火。两个人一不小心就容易发生冲突,该拿的食材没有拿,或者互相撞在一起o(╥﹏╥)o
但是这些不简单的操作,今天你都不用关心,就可以理解协程!
协程/Coroutine
协程可以让计算机程序在IO密集型的场景下,支持更多的请求,而且比多线程的方式,节省更多的资源,性能更优。
那什么是协程呢?维基百科的定义是——
协程(英语:coroutine)是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。
定义里面的关键词是协作式,挂起和恢复。是不是有点难理解?实际上它们就真真切切地存在分手厨房里面:
· 协作式,玩家们不停地协作制作订单~
· 挂起,当食材切好,放在锅里煮着的时候,这个订单就被“挂起”了~
· 当菜煮好的时候,你就去盛它,这个订单就被恢复了~
但是协程比分手厨房奇妙的地方在于,我们不用像游戏里的小人一样忙手忙脚,不知所措,只需要声明要制作的订单。计算机就会充当游戏里的小人帮我们把订单完成得漂漂亮亮。这些订单就是我们的代码,但是它们又不同于常见的代码。它们是借助于async, await构造的协程/coroutine,看起来像是同步的代码,但是计算机会用异步的方式执行。
什么是同步的代码,什么又是异步的方式呢?什么是异步的执行用同步的方式来编写代码?
异步的代码是什么?
首先什么叫同步和异步。同步是什么?
举个例子,我们看视频的时候,如果画面和声音不同步,会觉得很别扭。所以同步可以理解为多个对象具有相对应的关系。
异步我们生活中用得很少,它一般是在专业领域出现。比如异步IO,异步通信,英文是asynchronous。简单理解就是不是同步。(<--这不是废话嘛。)
类比分手厨房,订单的制作就是异步的方式——当出现一个订单的时候,我们有空的时候,就会去拿食材,然后切菜。但是如果其他菜已经做好了,我们就会放下当前的订单,去处理其他的订单。所以订单不是一开始制作,不停地烹饪,直到这个订单完成,才去制作其他订单,而是中间穿插了其他订单的制作。这就是异步方式地烹饪菜肴。
为了加深理解,让我们一起再看个例子——一周的工作
上了一周的班
比如我们有个函数AWeek,它要做的事情是打印周一,播放音乐,打印周二,刷个朋友圈,打印周三。
同步的代码的样子:
function AWeek() {
// 同步的代码,同步的方式
print('Monday')
play_music() // block call
print('Tuesday')
browse_moment() // block call
print('Wednesday')
}
play_music() 和browse_moment()是阻塞调用。程序会等着这些事完成,然后继续执行接下来的步骤。
如果它们是非阻塞调用,那么异步代码长什么样子呢?以Javascript 为例子,在没有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一个串一个。它们长得虽然相似,但是执行起来确实异步的,因为它们是协程。
执行时候,像下图那样执行,中间加入许多方框,做了好多事情。
所以异步的代码做事情仍按照顺序,但是并不是时间上相连的顺序,而是逻辑上的顺序。带有async, await, yield, promise的函数就是协程/coroutine,也就是异步的代码。
再议协程/Coroutine
现在,经过了分手厨房和一周的工作(AWeek),我们可以用更通俗易懂地方式来理解coroutine——
协程/Coroutine就是函数,只不过是可以suspend和resume的函数,也就是可以暂停这个函数的执行 (在suspend的地方直接返回到caller),去做其他事情,然后在恰当的时候恢复到你离开的位置继续开始运行。
让我们代入线程后看看是怎么实现的,下图左边灰色的coroutine(一个特别点的函数)。当它被调用的时候,被切成了两部分,蓝色框框和黄色框框:线程1在执行完成蓝色的框框后,就继续做其他事情了;第二部分黄色的框框被suspend了。当时机出现的时候,线程2(可以是线程1)就开始执行第二部分黄色的框框。
Coroutine in C++ 20
MicroStrategy Intelligence Server使用C++打造, 而C++ 20加入了协程/coroutine的支持。所以借助协程/coroutine,可以让我们强大的intelligence Server性能再上一个台阶,以及更容易编写和维护庞大的代码!
附注
特别鸣谢同事Roger带我玩分手厨房。
参考文档
https://store.steampowered.com/app/448510/Overcooked/?l=schinese&curator_clanid=28673811
https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B
https://zhuanlan.zhihu.com/p/237952115
https://zhuanlan.zhihu.com/p/237067072
关注了解更多微策略最新动态,行业资讯以及程序员日常