做为程序员,想必你多多少少听过协程这个词,这项技术近年来愈来愈多的出如今程序员的视野当中,尤为高性能高并发领域。当你的同窗、同事提到协程时若是你的大脑一片空白,对其毫无概念。。。python
那么这篇文章正是为你量身打造的。程序员
话很少说,今天的主题就是做为程序员,你应该如何完全理解协程。编程
咱们先来看一个普通的函数,这个函数很是简单:并发
def func():
print("a")
print("b")
print("c")编程语言
这是一个简单的普通函数,当咱们调用这个函数时会发生什么?函数
是否是很简单,函数func执行直到返回,并打印出:高并发
a
b
c性能
So easy,有没有,有没有!spa
很好!操作系统
注意这段代码是用python写的,但本篇关于协程的讨论适用于任何一门语言,咱们只不过刚好使用了python来用做示例,由于其足够简单。
那么协程是什么呢?
接下来,咱们就要从普通函数过渡到协程了。
和普通函数只有一个返回点不一样,协程能够有多个返回点。
这是什么意思呢?
void func() {
print("a")
暂停并返回
print("b")
暂停并返回
print("c")
}
普通函数下,只有当执行完print("c")这句话后函数才会返回,可是在协程下当执行完print("a")后func就会因“暂停并返回”这段代码返回到调用函数。
有的同窗可能会一脸懵逼,这有什么神奇的吗?我写一个return也能返回,就像这样:
void func() {
print("a")
return
print("b")
暂停并返回
print("c")
}
直接写一个return语句确实也能返回,但这样写的话return后面的代码都不会被执行到了。
协程之因此神奇就神奇在当咱们从协程返回后还能继续调用该协程,而且是从该协程的上一个返回点后继续执行。
这足够神奇吧,就比如孙悟空说一声“定”,函数就被暂停了:
void func() {
print("a")
定
print("b")
定
print("c")
}
这时咱们就能够返回到调用函数,当调用函数何时想起该协程后能够再次调用该协程,该协程会从上一个返回点继续执行。
Amazing,有没有,有没有!
很是好!
只不过孙大圣使用的口诀“定”字,在编程语言中通常叫作yield(其它语言中可能会有不一样的实现,但本质都是同样的)。
须要注意的是,当普通函数返回后,进程的地址空间中不会再保存该函数运行时的任何信息,而协程返回后,函数的运行时信息是须要保存下来的,那么函数的运行时状态到底在内存中是什么样子呢,关于这个问题你能够参考_这里_。
接下来,咱们就用实际的代码看一看协程。
下面咱们使用一个真实的例子来说解,语言采用python,不熟悉的同窗不用担忧,这里不会有理解上的门槛。
在python语言中,这个“定”字一样使用关键词yield,这样咱们的func函数就变成了:
void func() {
print("a")
yield
print("b")
yield
print("c")
}
注意,这时咱们的func就再也不是简简单单的函数了,而是升级成为了协程,那么咱们该怎么使用呢,很简单:
1 def A():
2 co = func() # 获得该协程
3 next(co) # 调用协程
4 print("in function A") # do something
5 next(co) # 再次调用该协程
咱们看到虽然func函数没有return语句,也就是说虽然没有返回任何值,可是咱们依然能够写co = func()这样的代码,意思是说co就是咱们拿到的协程了。
接下来咱们调用该协程,使用next(co),运行函数A看看执行到第3行的结果是什么:
a
显然,和咱们的预期同样,协程func在print("a")后因执行yield而暂停并返回函数A。
接下来是第4行,这个毫无疑问,A函数在作一些本身的事情,所以会打印:
a
in functino A
接下来是重点的一行,当执行第5行再次调用协程时该打印什么呢?
若是func是普通函数,那么会执行func的第一行代码,也就是打印a。
但func不是普通函数,而是协程,咱们以前说过,协程会在上一个返回点继续运行,所以这里应该执行的是func函数第一个yield以后的代码,也就是print("b")。
a
in functino A
b
看到了吧,协程是一个很神奇的函数,它会本身记住以前的执行状态,当再次调用时会从上一次的返回点继续执行。
神奇不神奇,厉害不厉害!
Very Good.
为了让你更加完全的理解协程,咱们使用图形化的方式再看一遍,首先是普通的函数调用:
在该图中,方框内表示该函数的指令序列,若是该函数不调用任何其它函数,那么应该从上到下依次执行,但函数中能够调用其它函数,所以其执行并非简单的从上到下,箭头线表示执行流的方向。
从图中咱们能够看到,咱们首先来到funcA函数,执行一段时间后发现调用了另外一个函数funcB,这时控制转移到该函数,执行完成后回到main函数的调用点继续执行。
这是普通的函数调用。
接下来是协程。
在这里,咱们依然首先在funcA函数中执行,运行一段时间后调用协程,协程开始执行,直到第一个挂起点,此后就像普通函数同样返回funcA函数,funcA函数执行一些代码后再次调用该协程,注意,协程这时就和普通函数不同了,协程并非从第一条指令开始执行而是从上一次的挂起点开始执行,执行一段时间后遇到第二个挂起点,这时协程再次像普通函数同样返回funcA函数,funcA函数执行一段时间后整个程序结束。
怎么样,神奇不神奇,和普通函数不一样的是,协程能知道本身上一次执行到了哪里。
如今你应该明白了吧,协程会在函数被暂停运行时保存函数的运行状态,并能够从保存的状态中恢复并继续运行。
很熟悉的味道有没有,这不就是操做系统对线程的调度嘛,线程也能够被暂停,操做系统保存线程运行状态而后去调度其它线程,此后该线程再次被分配CPU时还能够继续运行,就像没有被暂停过同样。
只不过线程的调度是操做系统实现的,这些对程序员都不可见,而协程是在用户态实现的,对程序员可见。
这就是为何有的人说能够把协程理解为用户态线程的缘由。
此处应该有掌声。
也就是说如今程序员能够扮演操做系统的角色了,你能够本身控制协程在何时运行,何时暂停,也就是说协程的调度权在你本身手上。
在协程这件事儿上,调度你说了算。
当你在协程中写下yield的时候就是想要暂停改协程,当使用next()时就是要再次运行该协程。
如今你应该理解为何说函数只是协程的一种特例了吧,函数其实只是没有挂起点的协程而已。
有的同窗可能认为协程是一种比较新的技术,然而其实协程这种概念早在1958就已经提出来了,要知道这时线程的概念都尚未提出来。
到了1972年,终于有编程语言实现了这个概念,这两门编程语言就是Simula 67 以及Scheme。
但协程这个概念始终没有流行起来,甚至在1993年还有人考古同样专门写论文挖出协程这种古老的技术。
由于这一时期尚未线程,若是你想在操做系统写出并发程序那么你将不得不使用相似协程这样的技术,后来线程开始出现,操做系统终于开始原生支持程序的并发执行,就这样,协程逐渐淡出了程序员的视线。
直到近些年,随着互联网的发展,尤为是移动互联网时代的到来,服务端对高并发的要求愈来愈高,协程再一次重回技术主流,各大编程语言都已经支持或计划开始支持协程。
如今你应该对协程有一个清晰的认知了吧。
到这里你应该已经理解协程究竟是怎么一回事,可是,依然有一个问题没有解决,为何协程这种技术又一次重回视线,协程适用于什么场景下呢?该怎么使用呢?
关于这些问题,下一篇文章将会给你答案。
但愿这篇对你理解协程有所帮助。