python入門三十二天——協程 異步IO\asyncio、async/await、aiohttp

asyncio

asyncio是Python 3.4版本引入的標準庫,直接內置了對異步IO的支持。html

asyncio的編程模型就是一個消息循環。咱們從asyncio模塊中直接獲取一個EventLoop的引用,而後把須要執行的協程扔到EventLoop中執行,就實現了異步IO。python

asyncio實現Hello world代碼以下:web

import asyncio

@asyncio.coroutine
def hello():
    print("Hello world!")
    # 異步調用asyncio.sleep(1):
    r = yield from asyncio.sleep(1)
    print("Hello again!")

# 獲取EventLoop:
loop = asyncio.get_event_loop()
# 執行coroutine
loop.run_until_complete(hello())
loop.close()

  

@asyncio.coroutine把一個generator標記爲coroutine類型,而後,咱們就把這個coroutine扔到EventLoop中執行。編程

hello()會首先打印出Hello world!,而後,yield from語法可讓咱們方便地調用另外一個generator。因爲asyncio.sleep()也是一個coroutine,因此線程不會等待asyncio.sleep(),而是直接中斷並執行下一個消息循環。當asyncio.sleep()返回時,線程就能夠從yield from拿到返回值(此處是None),而後接着執行下一行語句。服務器

asyncio.sleep(1)當作是一個耗時1秒的IO操做,在此期間,主線程並未等待,而是去執行EventLoop中其餘能夠執行的coroutine了,所以能夠實現併發執行。網絡

咱們用Task封裝兩個coroutine試試:併發

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()

  觀察執行過程:app

Hello world! (<_MainThread(MainThread, started 140735195337472)>)
Hello world! (<_MainThread(MainThread, started 140735195337472)>)
(暫停約1秒)
Hello again! (<_MainThread(MainThread, started 140735195337472)>)
Hello again! (<_MainThread(MainThread, started 140735195337472)>)

  

由打印的當前線程名稱能夠看出,兩個coroutine是由同一個線程併發執行的。框架

若是把asyncio.sleep()換成真正的IO操做,則多個coroutine就能夠由一個線程併發執行。異步

咱們用asyncio的異步網絡鏈接來獲取sina、sohu和163的網站首頁:

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()

  執行結果以下:

wget www.sohu.com...
wget www.sina.com.cn...
wget www.163.com...
(等待一段時間)
(打印出sohu的header)
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html
...
(打印出sina的header)
www.sina.com.cn header > HTTP/1.1 200 OK
www.sina.com.cn header > Date: Wed, 20 May 2015 04:56:33 GMT
...
(打印出163的header)
www.163.com header > HTTP/1.0 302 Moved Temporarily
www.163.com header > Server: Cdn Cache Server V2.0

  

可見3個鏈接由一個線程經過coroutine併發完成。

小結

asyncio提供了完善的異步IO支持;

異步操做須要在coroutine中經過yield from完成;

多個coroutine能夠封裝成一組Task而後併發執行。


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

讓咱們對比一下上一節的代碼:

@asyncio.coroutine
def hello():
    print("Hello world!")
    r = yield from asyncio.sleep(1)
    print("Hello again!")

  用新語法從新編寫以下:

async def hello():
    print("Hello world!")
    r = await asyncio.sleep(1)
    print("Hello again!")

  剩下的代碼保持不變。

小結

Python從3.5版本開始爲asyncio提供了asyncawait的新語法;

注意新語法只能用在Python 3.5以及後續版本,若是使用3.4版本,則仍需使用上一節的方案。

aiohttp

asyncio能夠實現單線程併發IO操做。若是僅用在客戶端,發揮的威力不大。若是把asyncio用在服務器端,例如Web服務器,因爲HTTP鏈接就是IO操做,所以能夠用單線程+coroutine實現多用戶的高併發支持。

asyncio實現了TCP、UDP、SSL等協議,aiohttp則是基於asyncio實現的HTTP框架。

咱們先安裝aiohttp

pip install aiohttp

  

而後編寫一個HTTP服務器,分別處理如下URL:

  • / - 首頁返回b'<h1>Index</h1>'

  • /hello/{name} - 根據URL參數返回文本hello, %s!

代碼以下:

import asyncio

from aiohttp import web

async def index(request):
    await asyncio.sleep(0.5)
    return web.Response(body=b'<h1>Index</h1>')

async def hello(request):
    await asyncio.sleep(0.5)
    text = '<h1>hello, %s!</h1>' % request.match_info['name']
    return web.Response(body=text.encode('utf-8'))

async def init(loop):
    app = web.Application(loop=loop)
    app.router.add_route('GET', '/', index)
    app.router.add_route('GET', '/hello/{name}', hello)
    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
    print('Server started at http://127.0.0.1:8000...')
    return srv

loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()

  注意aiohttp的初始化函數init()也是一個coroutineloop.create_server()則利用asyncio建立TCP服務。

相關文章
相關標籤/搜索