Python语言是支持协程的,下面我们简单介绍一下,并且举几个例子
1.什么是协程(Coroutine)?
协程又称为微线程,协程的完成主要靠yield关键字,协程执行过程中,在子程序内部可中断,然后
转而执行别的子程序,在适当的时候再返回来接着执行。
2.协程的优势?
(1) 我们知道在并发中使用多线程线程进行任务切换需要上下文的切换,需要一些开销,甚至有些时候
单核中使用多线程的效率没有单线程效率高(线程对象的创建,以及上下文的切换消耗性能占主导
作用),解决这个问题的方法之一就是使用协程。
(2) Python的多线程很鸡肋(由于存在GIL全局解释器锁,因此主张在计算密集型使用多进程,IO密集型
推荐使用多线程),我们可以使用多进程+协程的方式,即充分利用多核,又充分发挥协程的高效率,
可获得较好的性能。
(3) 不需要多线程的锁机制,当然也可以使用一些原子类(CAS算法), 因为协程只有一个线程,
也不存在同时写变量冲突,在协程中控制共享资源不需要加锁,只需要判断状态就好了。
3.Python中的协程
Python对协程的支持是通过generator实现的
在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。
但是Python的yield不但可以返回一个值,它还可以接受调用者发出的参数。
1.带有yield关键字的函数自动变成生成器
2.生成器被调用时不会立即执行
3.对于生成器,当调用函数next(generator)时,将获得生成器yield后面表达式的值;
4.当生成器已经执行完毕时,再次调用next函数,生成器会抛出StopIteration异常
下面一个简单的例子:
# -*- coding: utf-8 -*-
import time
from inspect import isgeneratorfunction
import types
from collections import Iterable
def run1():
while True:
print("Tell me to your heart")
time.sleep(1)
yield 1
def run2():
while True:
print("Tell me to your soul")
time.sleep(1)
yield 2
if __name__ == "__main__":
# 每次调用runx()函数都会生成一个新的generator实例,各实例互不影响
t1 = run1()
t2 = run2()
print(isgeneratorfunction(run1)) # runx()函数是一个generator函数
print(isinstance(run1, types.GeneratorType))
print(isinstance(t1, types.GeneratorType)) # t1是调用run1()返回的一个generator实例
print(isinstance(run1, Iterable))
print(isinstance(t2, Iterable)) # t2是可迭代的
help(t1)
while True:
print(next(t1))
print(next(t2))
"""
result:
True
False
True
False
True
Help on generator object:
run1 = class generator(object)
| Methods defined here:
|
| __del__(...)
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __iter__(self, /)
| Implement iter(self).
|
| __next__(self, /)
| Implement next(self).
|
| __repr__(self, /)
| Return repr(self).
|
| close(...)
| close() -> raise GeneratorExit inside generator.
|
| send(...)
| send(arg) -> send 'arg' into generator,
| return next yielded value or raise StopIteration.
|
| throw(...)
| throw(typ[,val[,tb]]) -> raise exception in generator,
| return next yielded value or raise StopIteration.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| gi_code
|
| gi_frame
|
| gi_running
|
| gi_yieldfrom
| object being iterated by yield from, or None
Tell me to your heart
1
Tell me to your soul
2
Tell me to your heart
1
Tell me to your soul
2
Tell me to your heart
1
"""
可以看到t1,t2对象的一些来自于迭代器的魔法方法。
之前在Java多线程专栏中我们已经实战过生产者-消费者模型,使用的是Java的notify/wait机制,
现在改用Python中的协程(Java中也有的,如Kilim、quasar 、PicoThreads框架,以后并发专栏也会讲解的;
只不过Java语言级别原生不支持协程。这里对并发感兴趣的可以去看看go,对于高并发支持真的是棒,
我也刚开始接触go),生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回
生产者继续生产,效率极高。
# -*- coding: utf-8 -*-
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print("[CONSUMER] Consuming %s..." % n)
r = "200 OK"
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print("[PRODUCER] Producint %s..." % n)
r = c.send(n)
print("[PRODUCER] Consumer return: %s" % r)
c.close()
c = consumer()
produce(c)
"""
result:
[PRODUCER] Producint 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producint 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producint 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producint 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producint 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
[Finished in 0.6s]
"""
注意到consumer
函数是一个generator
,把一个consumer
传入produce
后:
-
首先调用
c.send(None)
启动生成器; -
然后,一旦生产了东西,通过
c.send(n)
切换到consumer
执行; -
consumer
通过yield
拿到消息,处理,又通过yield
把结果传回; -
produce
拿到consumer
处理的结果,继续生产下一条消息; -
produce
决定不生产了,通过c.close()
关闭consumer
,整个过程结束。
整个流程无锁,由一个线程执行,produce
和consumer
协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
最后套用Donald Knuth的一句话总结协程的特点:
“子程序就是协程的一种特例。”