我们在golang关于goroutines的文章中已经简单介绍了coroutines的概念,我们再简单回顾一下。协程也叫微线程,英文名Coroutine。它可以像线程一样进行调度,但不同的是线程的启动和调度需要由操作系统来处理。而且线程的启动和销毁需要涉及一些操作系统的变量申请和销毁处理,耗时较长。至于协程,它的调度和销毁都是由程序自己控制的,所以更轻量、更灵活。协程有这么多优点,但也有一些缺点。最大的缺点是它们需要编程语言的支持。否则,开发人员需要使用一些方法来实现协程。对于大多数语言,不支持此机制。因为go语言天生就支持协程,而且支持得很好,所以广受好评,在短短几年内迅速流行起来。对于Python来说,它有一个巨大的GIL先天问题。GIL是Python的全局锁。在其限制下,一个Python进程一次只能执行一个线程,即使在多核机器上也是如此。这极大地影响了Python的性能,尤其是在CPU密集型工作上。所以为了提高Python的性能,很多开发者想出了使用多进程+协程的方式。起初,它是由开发人员自己实现的。后来在Python3.4版本中,也正式包含了这个功能。因此,可以说Python是一门支持协程的语言。生成器(generator)我们在上一篇文章中也介绍了生成器。为什么我们在引入协程的时候需要用到生成器呢?因为Python协程底层是通过generator实现的。通过生成器实现协程的原因也很简单。我们都知道协程需要切换挂起,而生成器中有一个yield关键字,正好可以实现这个功能。所以一开始用Python开发协程函数的那些程序员是通过generator来实现的。想要了解Python中协程的使用,就得从最原始的生成器说起。生成器我们很熟悉,它本质上是一个带有关键字yield的函数。deftest():n=0while<10:val=yieldnprint('val={}'.format(val))n+=1如果这个函数中没有yield语句,那么就是一个普通的Python函数。添加语句val=yieldn后,它有什么变化?我们尝试运行一下:#调用测试函数得到一个生成器g=test()print(next(g))print(next(g))print(next(g))得到这样的结果:输出0,1、2很好理解,由next(g)返回,这也是generator的标准用法。奇怪的是为什么val=None?val不应该等于n吗?这里看不懂很正常,因为这里涉及到一个新的用法,就是generator的send方法。当我们在yield语句前加上一个变量名,它的意思其实就是返回yield之后的内容,然后从外界接收一个变量。也就是说,当我们执行next(g)时,我们会得到yield之后的数字,而当我们执行g.send()时,传入的值会赋值给yield之前的数字。比如我们把执行的代码改成这样:g=test()print(next(g))g.send('abc')print(next(g))print(next(g))下面看执行result,你会发现是这样的:val的第一行不再是None,而是我们刚刚传入的abc,队列调度生成器每次执行完yield语句后自然就挂掉了。我们可以使用它作为协程进行调度。我们可以自己实现一个简单的队列来模拟这个过程。首先,我们声明一个双端队列。每次我们从队列左首拿到任务,调度执行直到挂起,放到队尾。相当于轮询所有任务,循环执行,整个过程不涉及任何线程的创建和销毁过程。classScheduler:def__init__(self):self._queue=deque()defnew_task(self,task):self._queue.append(task)defrun(self):whileself._queue:#从队列左侧获取tasktask=self每一次。_queue.popleft()try:#下次执行后放入队列右侧next(task)self._queue.append(task)exceptStopIteration:passsch=Scheduler()sch.new_task(test(5))sch.new_task(test(10))sch.new_task(test(8))sch.run()这只是一个很简单的调度方法。其实结合yieldfrom和send函数,我们还可以实现更复杂的协程调度方式。但我们不需要一一穷尽。我们只需要了解最基本的方法即可。毕竟我们在使用协程的时候一般不会自己实现,会通过官方原生的工具库来实现。@asyncio.coroutine在Python3.4之后的版本中,我们可以使用@asyncio.coroutine注解将一个函数封装成一个generator来协程执行。Python在吸收了协程的概念后,对生成器和协程进行了区分。用@asyncio.coroutine注解的函数称为协程函数。我们可以使用iscoroutinefunction()方法来判断一个函数是否是协程函数。这个协程函数返回的生成器对象称为协程对象。我们可以使用iscoroutine方法来判断一个对象是否是协程对象。比如我给刚才写的函数加上注解,然后执行这两个函数,就会得到True:importasyncio@asyncio.coroutinedeftest(k):n=0while
