Python中的协程是什么

协程
python 在GIL下,只有一个线程可以在同一时间运行,所以对于CPU计算密集的程序,线程之间的切换成本被拖累,而以I/O为瓶颈的程序是协程所擅长的:
Python中的协程经历了很长一段时间的发展。它可能经历了以下三个阶段:
1.原始生成器变形yield/send;
2.引入@asyncio.coroutine和yield from;
3.在最近的Python3.5版本中引入async/await关键词。
(1)从yield开始
先看一个普通的计算斐波那契续列的代码
deffibs(n): res=[0]*n index=0 a=0 b=1 whileindex<n: res[index]=b a,b=b,a+b index+=1 returnres forfib_resinfibs(20): print(fib_res)
如果我们只需要获得斐波那契序列的N位,或者只是想产生斐波那契序列,那么上述传统方法将消耗更多的内存。
这时,yield就派上用场了。
deffib(n): index=0 a=0 b=1 whileindex<n: yieldb a,b=b,a+b index+=1 forfib_resinfib(20): print(fib_res)
当一个函数包含yield语句时,python会自动将其识别为生成器。此时,fib(20)并没有真正调用函数体,而是通过函数体生成了生成器对象的例子。
yield可以在这里保留fib函数的计算场景,暂停fib计算并返回b。当fib放入for..in周期时,每个周期都会调用next(fib(20)唤醒生成器,执行到下一个yield语句,直到抛出stopiteration异常。这种异常会被for循环捕获,导致跳出循环。
(2) Send来了
从上面的程序可以看出,只有数据通过yield从fib(20)流向外部for循环;如果数据可以发送到fib(20),那么协程就可以在python中实现。
相关推荐:Python视频教程
因此,Python中的生成器具有send函数,yield表达式也具有返回值。
我们用这个特性来模拟慢速斐波那契数列的计算:
importtime
importrandom
defstupid_fib(n):
index=0
a=0
b=1
whileindex<n:
sleep_cnt=yieldb
print('letmethink{0}secs'.format(sleep_cnt))
time.sleep(sleep_cnt)
a,b=b,a+b
index+=1
print('-'*10+'testyieldsend'+'-'*10)
N=20
sfib=stupid_fib(N)
fib_res=next(sfib)
whileTrue:
print(fib_res)
try:
fib_res=sfib.send(random.uniform(0,0.5))
exceptStopIteration:
breakpython 并发编程
Python 在2时代,Twisted主要用于高性能网络编程、Tornado和Gevent这三个库,但它们的异步代码既不兼容也不能移植。
asyncio是Python 3.4版本引入的标准库直接内置了对异步IO的支持。
asyncio的编程模型是一个新闻循环。我们直接从asyncio模块中获得Eventloop的引用,然后将要执行的协程扔进Eventlop中执行,实现异步IO。
Python在3.4中引入了协程的概念,但这仍然是基于生成器对象。
Python 3.5添加了async和await两个关键字,分别用于替换asyncio.coroutine和yield from。
python3.5.确定了协程的语法。下面将简要介绍asyncio的使用情况。不仅是asyncio实现了协程,tornado和gevent也实现了类似的功能。
(1)协程定义
Hellolloo实现asyncio world代码如下:
importasyncio
@asyncio.coroutine
defhello():
print("Helloworld!")
#异步调用asyncio.sleep(1):
r=yieldfromasyncio.sleep(1)
print("Helloagain!")
#Eventlooop获得Eventlop:
loop=asyncio.get_event_loop()
#执行coroutine执行coroutine
loop.run_until_complete(hello())
loop.close()@asyncio.coroutine将generator标记为coroutine类型,然后我们将coroutine扔进eventlop中执行。 hello()首先打印Hello world!,然后,yield from语法可以让我们轻松地调用另一个generator。因为asyncior.sleep()也是coroutine,所以线程不会等asyncioio.sleep(),但直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程可以从yield返回 from获得返回值(这里是None),然后执行下一行语句。
asyncio.sleep(1)它被认为是一个需要1秒钟的IO操作。在此期间,主线程没有等待,而是执行EventLoop中可执行的其他coroutine,因此可以实现并发执行。
我们试着用Task包装两个coroutine:
importthreading
importasyncio
@asyncio.coroutine
defhello():
print('Helloworld!(%s)'%threading.currentThread())
yieldfromasyncio.sleep(1)
print('Helloagain!(%s)'%threading.currentThread())
loop=asyncio.get_event_loop()
tasks=[hello(),hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()观察执行过程:
Helloworld!(<_MainThread(MainThread,started140735193772>) Helloworld!(<_MainThread(MainThread,started140735193772>) (暂停约1秒) Helloagain!(<_MainThread(MainThread,started140735193772>) Helloagain!(<_MainThread(MainThread,started140735193772>)
从目前打印的线程名称可以看出,两个coroutine是同一线程并发执行的。
如果把asyncioio放在asy的话.sleep()用真实的IO操作代替,多个coroutine可以通过一个线程并发执行。
asyncio案例实战实战
我们使用asyncio的异步网络连接来获取sina、sohu和163网站首页:
async_wget.py
importasyncio
@asyncio.coroutine
defwget(host):
print('wget%s...'%host)
connect=asyncio.open_connection(host,80)
reader,writer=yieldfromconnect
header='GET/HTTP/1.0\r\nHost:%s\r\n\r\n'%host
writer.write(header.encode('utf-8'))
yieldfromwriter.drain()
whileTrue:
line=yieldfromreader.readline()
ifline==b'\r\n':
break
print('%sheader>%s'%(host,line.decode('utf-8').rstrip()))
#Ignorethebody,closethesocket
writer.close()
loop=asyncio.get_event_loop()
tasks=[wget(host)forhostin['www.sina.com.cn','www.sohu.com','www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()结果信息如下:
wgetwww.sohu.com... wgetwww.sina.com.cn... wgetwww.163.com... (等待一段时间) (打印sohuheader) www.sohu.comheader>HTTP/1.1200OK www.sohu.comheader>Content-Type:text/html ... (打印sinaheader) www.sina.com.cnheader>HTTP/1.1200OK www.sina.com.cnheader>Date:Wed,20May201504:56:33GMT ... (打印163header) www.163.comheader>HTTP/1.0302Movedtemprarily www.163.comheader>Server:Cdncacheserververververv2.0 ...
可以看出,通过coroutine并发完成三个连接。
小结
asyncio提供完善的异步IO支持;
异步操作需要通过yield在coroutine中进行异步操作 完成from;
多个coroutine可以封装成一组task,然后并发执行。
