##前言
想要实现协程的有很多办法,例如tornado、gevent、aysncio,现在主要说asyncio
定义一个协程
定义一个协程有两种方式,一种是python3.4提供的 @asyncio.coroutine与yield方式,python3.5提供了新的方法async与await方法,当然是学习更新的了
import asyncio
async def xc():
print('这是一个协程函数')
loop = asyncio.get_event_loop()
loop.run_until_complete(xc())
xc 就是一个协程函数, 需要注意的是虽然xc是一个函数,但是不能直接运行,需要把协程加入到时间循环(loop)里,由loop调用协程 可以看到这个协程的输出
这是一个协程函数
asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task,run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task,task是Future的子类。isinstance(task, asyncio.Future)将会输出True。
await
与 async相对应的就是await, 使用await可以让耗时操作(一般是io操作)挂起,让cpu让出使用权,直到await的耗时操作完成,才将cpu的使用权归还。
import asyncio
async def xc():
print('xc begin')
await asyncio.sleep(2)
print('xc end')
loop = asyncio.get_event_loop()
loop.run_until_complete(xc())
在 sleep的时候,使用await让出控制权。即当遇到阻塞调用的函数的时候,使用await方法将协程的控制权让出, 以便loop调用其他的协程。如果多个协程同时运行的情况,那就实行了我想想用asyncio要实现的核心功能,并发。
并发
使用asyncio想主要实现的功能就是实现并发,想实现多个并发,就需要多个协程来完成任务,每当任务阻塞的时候就await,然后loop将cpu分配给其余的协程
async def xc1():
print('xc1 begin')
await asyncio.sleep(3)
print('xc1 end')
async def xc2():
print('xc2 begin')
await asyncio.sleep(1)
print('xc2 end')
tasks = [
asyncio.ensure_future(xc1()),
asyncio.ensure_future(xc2()),
]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
这里有两个协程,其中xc1先执行,但是阻塞3秒,这个时候xc2执行,但是xc2阻塞一秒,所以虽然是xc1先执行,但是xc2先执行完,
输出:
xc1 begin
xc2 begin
xc2 end
xc1 end
run_until_complete 与 run_forever
前面的例子在获取了loop之后都是通过loop.run_until_complete来运行loop,从名字也可以看出,他是运行直到完成之后返回,我们也可以用另一种方式来运行loop
import asyncio
async def xc():
print('xc begin')
await asyncio.sleep(2)
print('xc end')
loop = asyncio.get_event_loop()
loop.create_task(xc())
# asyncio.ensure_future(xc())
loop.run_forever()
这个程序不会退出,想让他退出只有一个办法,就是调用loop.stop()
loop.run_forever()
loop.stop()
但是这种法法也不可以,因为run_forever不结束,stop调用不到,所以只能在协程中调用stop
async def xc(loop):
await asyncio.sleep(3)
loop.stop()
还有一个问题,就是loop只要不清理,就会一直存在,所以当调用完协程之后,最好调用loop.close清理
loop = asyncio.get_event_loop()
print(loop)
loop.close()
print(loop)
输出
<_UnixSelectorEventLoop running=False closed=False debug=False>
<_UnixSelectorEventLoop running=False closed=True debug=False>
wait 与 gather
当我们同时运行多个task的时候,用到了asyncio.wait()方法,还可以用asyncio.gather()来执行。
async def xc1():
print('xc1 begin')
await asyncio.sleep(3)
print('xc1 end')
async def xc2():
print('xc2 begin')
await asyncio.sleep(1)
print('xc2 end')
tasks = [
asyncio.ensure_future(xc1()),
asyncio.ensure_future(xc2()),
]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))
它和wait的输出是一样的
xc1 begin
xc2 begin
xc2 end
xc1 end
那他们的区别是什么那,在你的task有return值的时候就会有区别,就是asyncio.gather偏重于结果,他直接等待并按照固定的顺序返回结果,而asyncio.wait则需要手动处理结果。
async def xc1():
print('xc1 begin')
await asyncio.sleep(3)
print('xc1 end')
return 'xc1'
async def xc2():
print('xc2 begin')
await asyncio.sleep(1)
print('xc2 end')
return 'xc2'
tasks = [
asyncio.ensure_future(xc1()),
asyncio.ensure_future(xc2()),
]
loop=asyncio.get_event_loop()
result = loop.run_until_complete(asyncio.gather(*tasks))
print(result)
输出
xc1 begin
xc2 begin
xc2 end
xc1 end
['xc1', 'xc2']
它是按照任务的排序输出,如果使用asyncio.wait()那,就需要自己处理结果
async def xc1():
print('xc1 begin')
await asyncio.sleep(3)
print('xc1 end')
return 'xc1'
async def xc2():
print('xc2 begin')
await asyncio.sleep(1)
print('xc2 end')
return 'xc2'
tasks = [
asyncio.ensure_future(xc1()),
asyncio.ensure_future(xc2()),
]
loop=asyncio.get_event_loop()
result, _ = loop.run_until_complete(asyncio.wait(tasks))
for r in result:
print(r.result())
输出
xc1 begin
xc2 begin
xc2 end
xc1 end
xc1
xc2
aiohttp
现在知道了asyncio的使用,那怎么用一个asyncio实现一个爬虫那。
import asyncio
import aiohttp
async def get_title(i):
url = 'http://cdn.3commas.cn/keyboards/0000077c-2be0-4fd1-8b2f-042d731e8e2c_1.json'
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(i)
content = await resp.json()
print(content)
loop = asyncio.get_event_loop()
fun_list = (get_title(i) for i in range(10))
loop.run_until_complete(asyncio.gather(*fun_list))
通过结果我们可以看到,输出结果几乎是同一时间打印,网络io并没有造成程序的阻塞。
与asyncio配合的还包括aioredis、aiomysql等等