1. 程式人生 > >【Python入門】50.非同步IO之 asyncio實現非同步操作

【Python入門】50.非同步IO之 asyncio實現非同步操作

摘要:如何通過asyncio實現非同步IO;用aiohttp模組編寫支援多使用者高併發的伺服器。

*寫在前面:為了更好的學習python,博主記錄下自己的學習路程。本學習筆記基於廖雪峰的Python教程,如有侵權,請告知刪除。歡迎與博主一起學習Pythonヽ( ̄▽ ̄)ノ *

非同步IO

asyncio

asyncio是Python 3.4版本引入的標準庫,直接內建了對非同步IO的支援。

我們只要從asyncio模組中獲取一個EventLoop的引用,然後把需要執行的協程放到EventLoop中執行,就可以實現非同步IO了。

我們看個簡單的例子:

import asyncio

@asyncio.coroutine
def A():
    print('Hello, A!')
    r = yield from asyncio.sleep(1)
    print('Hello again!')

@asyncio.coroutine
def B():
    print('Hello, B!')
    r = yield from asyncio.sleep(1)
    print('Hello again!')

loop = asyncio.get_event_loop()                   # 獲取EventLoop的引用
tasks = [A(),B()]                                 # 把兩個coroutine封裝起來
loop.run_until_complete(asyncio.wait(tasks))      # 把封裝好的coroutine放到EventLoop中執行
loop.close()

語句@asyncio.coroutine是把緊接的generator標記為協程coroutine型別。

語句yield from可以呼叫另一個generator,並且拿取返回值(這裡為None)。

語句asyncio.sleep(1)可以當作是一個耗時1秒的IO操作。

定義好coroutine之後,把它們封裝好,放入EventLoop中執行即可。

執行結果:

Hello, B!
Hello, A!
(間隔約1秒)
Hello again!
Hello again!

可以看到coroutine A和coroutine B是併發執行的。

如果把asyncio.sleep()換成真正的IO操作,就可以實現多個coroutine就由一個執行緒併發執行的非同步IO操作了。

我們用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
...

async/await

在Python3.5版本中,引入了新語法asyncawait,讓coroutine的程式碼更簡潔易讀。

其中:

async用於替換之前的@asyncio.coroutine

await用於替換之前的yield from

我們用更為簡潔的語法把上面的程式碼重新編寫一下:

import asyncio

async def wget(host):
    print('wget %s...' % 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.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

執行結果與上面一致。

需要注意的是,新語法是Python3.5及之後的版本使用,若是3.4及之前的版本,仍需要用之前的語法。

aiohttp

在上面的例子中,asyncio用在了客戶端上。

實際上asyncio多用在伺服器端上,例如Web伺服器。由於HTTP連線就是IO操作,通過asyncio可以實現多使用者的高併發支援。

aiohttp是基於asyncio實現的HTTP框架。

aiohttp沒有內建,需要通過pip下載安裝:

pip install aiohttp

我們試一下用aiohttp編寫一個伺服器,來處理下面兩個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>', content_type='text/html')

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'), content_type='text/html')

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)          # 建立TCP服務
    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()

執行之後,在瀏覽器中輸入http://localhost:8000/hello/xxx,結果如下: 在這裡插入圖片描述

小結

asyncio提供了完善的非同步IO支援;

非同步操作需要在coroutine中通過yield from完成;

coroutine放到asyncio提供EventLoop引用中執行,即可實現非同步操作;

在Python3.5及之後的版本中,語法@asyncio.coroutine替換成async,語法yield from替換成await

非同步IO更多用於伺服器端,通過aiohttp模組,可以簡單地編寫出支援多使用者高併發的伺服器。

以上就是本節的全部內容,感謝你的閱讀。

下一節內容:實戰

有任何問題與想法,歡迎評論與吐槽。

和博主一起學習Python吧( ̄▽ ̄)~*