Python学习—22 异步I/O

在同步IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。

如果IO请求需要大量时间执行的话,异步IO方式可以显著提高效率,因为在线程等待的这段时间内,CPU将会调度其他线程进行执行,如果没有其他线程需要执行的话,这段时间将会浪费掉。

协程

协程(Coroutine),又称微线程。

我们平常使用的函数又称子程序,是层级调用的,即A函数调用B,B函数又调用C,那么需要等C执行完毕返回,然后B程序执行完毕返回,最后A执行完毕。

协程看上去也是子程序,但是执行顺序和子程序不同:协程执行过程可以中断,同样是A函数调用B,但B可以执行一部分继续去执行A,然后继续执行B未执行完的部分。

协程看起来很像多线程。但协程最大的优势是极高的执行效率。因为线程需要互相切换,切换需要开销。且线程直接共享变量需要使用锁机制,因为协程只有一个线程,不存在同时写变量冲突。

Python对协程的支持是通过generator实现的。

在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。

但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。下面是一个典型的生产者-消费者模型:

  1. # coding: utf-8
  2. def consumer():
  3. r = ''
  4. while True:
  5. n = yield r
  6. print('[Consumer] Consuming %s' % n)
  7. r = '200 OK'
  8. def produce(c):
  9. c.send(None) #启动生成器
  10. i = 0
  11. while i < 5:
  12. i = i + 1
  13. print('[Produce] Start produce %s' % i)
  14. r = c.send(i)
  15. print('[Produce] Consumer return %s' % r)
  16. c = consumer() #生成器
  17. produce(c)

输出:

  1. [Produce] Start produce 1
  2. [Consumer] Consuming 1
  3. [Produce] Consumer return 200 OK
  4. [Produce] Start produce 2
  5. [Consumer] Consuming 2
  6. [Produce] Consumer return 200 OK
  7. [Produce] Start produce 3
  8. [Consumer] Consuming 3
  9. [Produce] Consumer return 200 OK
  10. [Produce] Start produce 4
  11. [Consumer] Consuming 4
  12. [Produce] Consumer return 200 OK
  13. [Produce] Start produce 5
  14. [Consumer] Consuming 5
  15. [Produce] Consumer return 200 OK

执行顺序:
1、发送None启动生成器consumer(),运行yield r,返回’’,程序中断;
2、第2次发送1,从上次运行结束的地方开始,先通过n = yield r接收到1,继续运行打印语句,再次运行到yield r,返回’200 OK’;
3、第3次发送2,从上次运行结束的地方开始,先通过n = yield r接收到2,继续运行打印语句,再次运行到yield r,返回’200 OK’;
4、…

大家可以使用 Intellij IDEA调试功能 进行单步运行观察执行流程。

整个流程由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

asyncio

asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。使用asyncio可以实现单线程并发IO操作。

@asyncio.coroutine把一个generator标记为coroutine类型:

  1. # coding: utf-8
  2. import asyncio
  3. @asyncio.coroutine
  4. def helloWorld(n):
  5. print('Hello world! %s' % n)
  6. r = yield from asyncio.sleep(3)
  7. print('Hello %s %s ' % (r, n))
  8. loop = asyncio.get_event_loop()
  9. tasks = [helloWorld(1), helloWorld(2), helloWorld(3), helloWorld(4)]
  10. loop.run_until_complete(asyncio.wait(tasks))
  11. loop.close()

输出:

  1. Hello world! 2
  2. Hello world! 3
  3. Hello world! 1
  4. Hello world! 4
  5. #(等待3秒左右)
  6. Hello None 2
  7. Hello None 1
  8. Hello None 3
  9. Hello None 4

程序先运行Hello world! ,然后由于asyncio.sleep()也是一个coroutine,线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

async/await

用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。

为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法asyncawait,可以让coroutine的代码更简洁易读。

请注意,asyncawait是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

  1. @asyncio.coroutine替换为async
  2. yield from替换为await

上节的代码用Python3.5写:

  1. # coding: utf-8
  2. import asyncio
  3. async def helloWorld(n):
  4. print('Hello world! %s' % n)
  5. r = await asyncio.sleep(3)
  6. print('Hello %s %s ' % (r, n))
  7. loop = asyncio.get_event_loop()
  8. tasks = [helloWorld(1), helloWorld(2), helloWorld(3), helloWorld(4)]
  9. loop.run_until_complete(asyncio.wait(tasks))
  10. loop.close()

aiohttp

aiohttp是基于asyncio实现的HTTP框架。

需要先安装:

  1. $ pip install aiohttp

控制台输出:

  1. Collecting aiohttp
  2. Downloading aiohttp-1.3.1-cp34-cp34m-win32.whl (147kB)
  3. 100% |████████████████████████████████| 153kB 820kB/s
  4. Collecting async-timeout>=1.1.0 (from aiohttp)
  5. Downloading async_timeout-1.1.0-py3-none-any.whl
  6. Collecting yarl>=0.8.1 (from aiohttp)
  7. Downloading yarl-0.9.6-cp34-cp34m-win32.whl (77kB)
  8. 100% |████████████████████████████████| 81kB 1.8MB/s
  9. Collecting chardet (from aiohttp)
  10. Downloading chardet-2.3.0-py2.py3-none-any.whl (180kB)
  11. 100% |████████████████████████████████| 184kB 890kB/s
  12. Collecting multidict>=2.1.4 (from aiohttp)
  13. Downloading multidict-2.1.4-cp34-cp34m-win32.whl (133kB)
  14. 100% |████████████████████████████████| 143kB 3.3MB/s
  15. Installing collected packages: async-timeout, multidict, yarl, chardet, aiohttp
  16. Successfully installed aiohttp-1.3.1 async-timeout-1.1.0 chardet-2.3.0 multidict-2.1.4 yarl-0.9.6

说明安装完成。

示例:

  1. # coding: utf-8
  2. import asyncio
  3. from aiohttp import web
  4. @asyncio.coroutine
  5. def index(request):
  6. return web.Response(body=b'Hello aiohttp!', content_type='text/html')
  7. @asyncio.coroutine
  8. def user(request):
  9. name = request.match_info['name']
  10. body = 'Hello %s' % name
  11. return web.Response(body=body.encode('utf-8'), content_type='text/html')
  12. pass
  13. @asyncio.coroutine
  14. def init(loop):
  15. # 创建app
  16. app = web.Application(loop=loop)
  17. #添加路由
  18. app.router.add_route('GET', '/', index)
  19. app.router.add_route('GET', '/user/{name}', user)
  20. #运行server
  21. server = yield from loop.create_server(app.make_handler(), '127.0.0.1', 9999)
  22. print('Server is running at http://127.0.0.1:9999 ...')
  23. loop = asyncio.get_event_loop()
  24. loop.run_until_complete(init(loop))
  25. loop.run_forever()

运行程序:

  1. $ python user_aiohttp.py
  2. Server is running at http://127.0.0.1:9999 ...

浏览器依次输入查看效果:
http://127.0.0.1:9999/
http://127.0.0.1:9999/user/aiohttp

更多知识可以查看aiohttp文档:
http://aiohttp.readthedocs.io/en/stable/

参考:
1、Python asyncio库的学习和使用
http://www.cnblogs.com/rockwall/p/5750900.html
2、异步IO
http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143208573480558080fa77514407cb23834c78c6c7309000

作者: 飞鸿影
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
出处:https://www.cnblogs.com/52fhy/p/6407121.html