协程
协程的用途在于IO,遇到IO就挂起,并发执行另一个任务,比如http请求,可以实现同时请求若干个网站的目的
但是很多经典库比如requests并不支持协程
生成器与协程
- PEP-0492 通过使用 async 关键字显式的对生成器和协程做了区分。
- 实际上,使用async显示声明协程(函数),并在函数内使用wait代替yield,一个协程变诞生了
生成器
- yield的功能类似于return,但是不同之处在于它返回的是生成器
- 生成器是通过一个或多个yield表达式构成的函数,每一个生成器都是一个迭代器(for循环或while循环)
- 如果一个函数包含yield关键字,这个函数就会变为一个生成器。
- 生成器并不会一次返回所有结果,而是每次遇到yield关键字后返回相应结果,并保留函数当前的运行状态,等待下一次的调用。
- 由于生成器也是一个迭代器,那么它就应该支持next方法来获取下一个值
1 | def func(): |
生产者-消费者模型
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高
1 | def consumer(): |
执行过程分析
- c = consumer(),生成generator
- c.send(None)其实等价于next(c),第一次执行时其实只执行到n = yield r就停止了,然后把r的值返回给调用者。用于启动生成器
- yield r是一个表达式,通过send(msg)被赋值,而send(msg)是有返回值的,返回值为:下一个yield r表达式的参数,即为r。
- produce一旦生产了东西,通过c.send(n)切换到consumer执行。consumer通过yield拿到消息,处理,又通过yield把结果传回。也就是说,c.send(1) 不但会给 c 传送一个数据,它还会等着下次 yield 从 c 中返回一个数据,它是有返回值的,一去一回才算完,拿到了返回的数据(200 OK)才继续下面执行。
- 整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
asyncio
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持
1 | import time |
两个hello()是由同一个线程并发执行的,返回结果为
1 | # 单线程依次执行print('Hello world!') |
async/await
async/await是自python 3.5开始后asyncio实现的新语法
- 把@asyncio.coroutine替换为async;
- 把yield from替换为await。
1 | async def hello(): |
aiohttp
asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。
如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。
asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。
1 | pip install aiohttp |
1 | import asyncio |
python并发之协程
python:利用asyncio进行快速抓取
玩转 Python 3.5 的 await/async
Python 3.5 协程究竟是个啥