asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。asyncio的异步操作,需要在coroutine中通过yield from完成。
event loop 对象包含两个部分:event 和 loop。event 负责 I/O 事件通知而 loop 负责循环处理 I/O 通知并在就绪时调用回调。这里 event 的含义与 select 中的 event mask 类似。
协程可以处理IO密集型程序的效率问题,但是处理CPU密集型不是它的长处,如要充分发挥CPU利用率可以结合多进程+协程。
先分析一个例子:
import asyncio
@asyncio.coroutine
def wget(host):
connect = asyncio.open_connection(host, 80)
reader, writer = yield from connect
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
writer.write(header.encode('utf-8'))
yield from writer.drain()
while True:
line = yield from reader.readline()
if line == b'\r\n':
break
print('%s header > %s' %(host, line.decode('utf-8').rstrip())) #rstrip() 删除 string 字符串末尾的指定字符(默认为空格)
writer.close()
loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.baidu.com', 'www.google.com', 'www.sina.com', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
@asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。
drain的官方解释:
drain() gives the opportunity for the loop to schedule the write operation and flush the buffer. It should especially be used when a possibly large amount of data is written to the transport, and the coroutine does not yield-from between calls to write().
在事件循环中刷新缓冲区,特别是在数据量很大的情况下,保证数据完整性.
yield from语法可以让我们方便地调用另一个generator,当connect 阻塞以后,不会等待,而是返回执行下一个消息循环,连接下一个链接
如果在header下方加一个打印print(header):
GET / HTTP/1.0
Host: www.sohu.com
GET / HTTP/1.0
Host: www.163.com
GET / HTTP/1.0
Host: www.baidu.com
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html;charset=UTF-8
www.sohu.com header > Connection: close
www.sohu.com header > Server: nginx
www.sohu.com header > Date: Thu, 13 Jul 2017 07:48:39 GMT
www.sohu.com header > Cache-Control: max-age=60
.........................
最后阻塞在google 网络链接错误后退出.
asynico/await
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。
async 是明确将函数声明为协程的关键字,即使没有await表达式,函数执行也会返回一个协程对象。
在协程函数内部,可以在某个表达式之前使用 await 关键字来暂停协程的执行,以等待某协程完成.
请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
把@asyncio.coroutine替换为async;
把yield from替换为await。
代码变成:
import asyncio
async def wget(host):
connect = asyncio.open_connection(host, 80)
reader, writer = await connect
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
writer.write(header.encode('utf-8'))
await writer.drain()
while True:
line = await reader.readline()
if line == b'\r\n':
break
print('%s header > %s' %(host, line.decode('utf-8').rstrip()))
writer.close()
loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()