前言
协程,又称微线程,纤程。英文名Coroutine。最近几年才在某些语言(如Lua)中得到广泛应用!
协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
- 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
- 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
- 因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
generator是什么?
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建generator有两种方式:
- 只要把一个列表生成式的[]改成(),就创建了一个generator
- 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator
python中定义协程
Python对协程的支持是通过generator实现的。在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。
yield
类似于java中的return,返回一个值,但是不同点在于下次运行函数的时候是从yield后面的代码开始运行的,因为yield关键字标记了一个函数为generator,所以又一个next方法进行迭代,碰到yield后就不执行了并返回这个yield后面的值例如:
def hello():
print('hello world')
yield '我暂停,并返回了'
print('我继续执行了')
yield '我又暂停了,并返回了'
执行:
h = hello()
print(next(h))
得到结果:
hello world
我暂停,并返回了
再执行:
h = hello()
print(next(h))
print(next(h))
得到结果:
hello world
我暂停,并返回了
我继续执行了
我又暂停了,并返回了
yield from
yield from是yield的升级版
def generator_1(title1):
yield title1
def generator_2(title):
yield from title
titles = ['python', 'java', 'c++']
for title in generator_1(titles):
print('生成器1:', title)
for title in generator_2(titles):
print('生成器2:', title)
生成器1: ['python', 'java', 'c++']
生成器2: python
生成器2: java
生成器2: c++
可以看出yield from是将对象一个一个的迭代出来的,如果我们将上面yield的例子修改成yield from可以看到一样的结果:
def hello():
print('hello world')
yield from '我暂停,并返回了'
print('我继续执行了')
yield from '我又暂停了,并返回了'
h = hello()
print(next(h))
print(next(h))
print(next(h))
hello world
我
暂
停
因为是一个一个出来的,所以可以推断处yield from 后面跟的是需要迭代的对象!所以:
yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器
Python使用协程解决异步IO
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。
讲异步io前,我们需要了解一个模型:
委托模型
def gen():
"""子生成器"""
yield 1
def gen1(gen):
"""委托生成器"""
yield from gen
def main():
"""调用方"""
g = gen()
g1 = gen1(g)
next(g1) # 预刺激生成器
g1.send(None) # 启动生成器
就是调用方将任务委托给一个中间委托生成器,委托生成器将任务派发给子生成器去完成的模型!
所以最简单的任务执行模式:
@asyncio.coroutine
def hello():
print('hello world')
yield from asyncio.sleep(1)
print('hello again')
loop = asyncio.get_event_loop()
loop.run_until_complete(hello())
loop.close()
hello world
#间隔了一秒
hello again
我们用asyncio.sleep(1)实现线程休眠一秒模拟任务执行
我们再来模拟最简单的异步任务执行:
@asyncio.coroutine
def hello():
print('Hello world! (%s)' % threading.currentThread())
yield from asyncio.sleep(1)
print('Hello again! (%s)' % threading.currentThread())
task = [hello(), hello()]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(task))
loop.close()
Hello world! (<_MainThread(MainThread, started 4508042688)>)
Hello world! (<_MainThread(MainThread, started 4508042688)>)
#中间间隔一秒
Hello again! (<_MainThread(MainThread, started 4508042688)>)
Hello again! (<_MainThread(MainThread, started 4508042688)>)
可以看到我们两个任务都在一个线程内完成的,这也是协程的特点,而且没有阻塞异步完成的!
我们再将子生成器模拟出来形成真正的任务异步:
final_result = {}
# 子生成器
def salesNum(key):
total = 0
nums = []
while True: # 使用while循环不断的从调用方接收值
x = yield
print(key, "- 销量统计:%s" % x)
if not x:
break
total += x
nums.append(x)
return total, nums
# 委托生成器
def sales(key):
while True:
final_result[key] = yield from salesNum(key)
print(key + '销量统计完成')
def perform():
data_set = {
'牙膏': [100, 200, 300],
'衣服': [400, 500, 600],
'鞋子': [700, 800, 900]
}
for key, data in data_set.items():
print('start key:', key)
s = sales(key)
next(s)
for i in data:
s.send(i)
s.send(None)
print('final_result:', final_result)
perform()
结果是:
start key: 牙膏
牙膏 - 销量统计:100
牙膏 - 销量统计:200
牙膏 - 销量统计:300
牙膏 - 销量统计:None
牙膏销量统计完成
start key: 衣服
衣服 - 销量统计:400
衣服 - 销量统计:500
衣服 - 销量统计:600
衣服 - 销量统计:None
衣服销量统计完成
start key: 鞋子
鞋子 - 销量统计:700
鞋子 - 销量统计:800
鞋子 - 销量统计:900
鞋子 - 销量统计:None
鞋子销量统计完成
final_result: {'牙膏': (600, [100, 200, 300]), '衣服': (1500, [400, 500, 600]), '鞋子': (2400, [700, 800, 900])}
这就是协程异步的操作
async/await简化异步IO
从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。
请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
- 把@asyncio.coroutine替换为async
- 把yield from替换为await
例子:
async def hello():
print('hello world (%s)' % threading.currentThread())
await asyncio.sleep(1)
print('hello again (%s)' % threading.currentThread())
loop = asyncio.get_event_loop()
loop.run_until_complete(hello())
loop.close()
hello world (<_MainThread(MainThread, started 4464768448)>)
hello again (<_MainThread(MainThread, started 4464768448)>)
总结
asyncio提供了完善的异步IO支持;异步操作需要在coroutine中通过yield from完成;多个coroutine可以封装成一组Task然后并发执行。协程并发与执行效率非常的高效,现在kotlin也支持协程并且优化很好了,所以建议采用协程执行异步操作!