在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。多线程和多进程虽然解决了并发问题,但是系统不能无上限的增加线程,由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,会导致性能下降。
为了解决CPU和IO设备的速度严重不匹配的问题,多线程和多进程只是解决这问题的一种方法。另一种方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了,一段时间后,当IO返回结果时,再通知CPU进行处理。
了解异步IO之前,我们先来了解协程,协程,又称微线程。
Python对协程的支持是通过生成器实现的,在生成器中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切回到生产者继续生产,效率极高
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('Producing %s...' % n)
r = c.send(n)
print('Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
输出:
Producing 1...
Consuming 1...
Consumer return: 200 OK
Producing 2...
Consuming 2...
Consumer return: 200 OK
Producing 3...
Consuming 3...
Consumer return: 200 OK
Producing 4...
Consuming 4...
Consumer return: 200 OK
Producing 5...
Consuming 5...
Consumer return: 200 OK
注意到consumer函数是一个生成器,把consumer传入produce后:
整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为协程,而非线程的抢占式多任务。
asyncio是Python3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。
import threading
import asyncio
@asyncio.coroutine
def hello():
print('Hello world! (%s)' % threading.currentThread())
yield from asyncio.sleep(1)
print('Hello again! (%s)' % threading.currentThread())
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
@asyncio.coroutine就是把hello这个生成器标记为协程,然后,我们就把这个协程扔到事件循环中执行。
输出:
Hello world! (<_MainThread(MainThread, started 11180)>)
Hello world! (<_MainThread(MainThread, started 11180)>)
(暂停约1秒)
Hello again! (<_MainThread(MainThread, started 11180)>)
Hello again! (<_MainThread(MainThread, started 11180)>)
由输出信息可以看出,两个协程是由同一个线程并发执行的,如果把asyncio.sleep()换成真正的IO操作,则多个协程就可以由一个线程并发执行。
用asyncio提供的asyncio.coroutine可以把一个生成器标记为协程类型,然后在协程内部用yield from调用另外一个协程,实现异步操作。
为了简化并更好的标识异步IO,从Python3.5开始引入了新的语法async和await。
async和await是针对协程的新语法,要使用新的语法,只需要做两步简单的替换:
用新语法重写上面的列子:
async def hello():
print('Hello world! (%s)' % threading.currentThread())
await asyncio.sleep(1)
print('Hello again! (%s)' % threading.currentThread())
其他的代码保持不变
asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大,如果把asyncio用在服务端,例如Web服务器,由于HTTP连接就是IO操作,因此可以使用单线程+协程实现多用户的高并发支持。
asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架
客户端举例:
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://python.org')
print(html)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
服务端举例:
from aiohttp import web
async def handle(request):
name = request.match_info.get('name', "Anonymous")
text = "Hello, " + name
return web.Response(text=text)
app = web.Application()
app.add_routes([web.get('/', handle),
web.get('/{name}', handle)])
web.run_app(app)