Python学习:异步IO:协程和asyncio

时间:2021-09-23 23:31:19

所谓协程就是在一个线程中切换子进程,相比多线程有如下好处:最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

python的协程通过generator支持的.

yield可以接受参数|传递参数.

send调用协程,第一次要使用send(None)启动generator.

下面使用协程实现简单的生产者-效果者模型.

def consumer():
r = ''
while True:
n = yield r
if not n:return
print('consuming the event %s'%n)
r = '200 OK'

def produce(c):
c.send(None)
for n in range(1,6):
print('producing event %s'%n)
r = c.send(n)
print('the consumer response %s'%r)
c.close()
c = consumer()
produce(c)

总结一下,线程就是特殊的协程.

但是只有协程还不够,还不足以实现异步IO,我们必须实现消息循环和状态的控制.这就要引入asynico,它直接内置了对异步IO的支持.asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

[0]:先看一个例子:

import asyncio,sys,time
@asyncio.coroutine
def hello():
time.sleep(3)
print("Hello world!")
# 异步调用asyncio.sleep(1):
r = yield from asyncio.sleep(2)
print("Hello again!")

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
loop.run_until_complete(asyncio.wait([hello() for i in range(3)]))
loop.close()

在这个例子中,可以发现一旦协程阻塞,就会中断当前的协程处理,然后切换到下一个消息处理,同时把阻塞的协程加入消息队列的后面.

注意,如果是你自己写的协程,eventloop好像会直接执行yield from后面的协程,不管该协程是否有阻塞的行为(可能不能识别?)具体怎么处理还没有学习到,以后再更新

下面是使用协程来实现异步网络连接:

import asyncio

@asyncio.coroutine
def wget(host):
print('wget %s...' % 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()))
# Ignore the body, close the socket
writer.close()

loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

[0]asyncio.open_connection接受host参数和port参数以及一些可选的关键字参数.返回一个reader和一个writer,redaer is a StreamReader instance; the writer is a StreamWriter instance.

writer.write就和socket.send差不多…

[1]:对于writer.drain()的用法,它会阻塞如果writer的buffer已经满了…当然在我们这个例子里面buffer是充足的,因为我们只发送了几个GET请求。

When the size of the transport buffer reaches the high-water limit (the protocol is paused), block until the size of the buffer is drained down to the low-water limit and the protocol is resumed. When there is nothing to wait for, the yield-from continues immediately.