【Python入門(mén)】50.異步IO之 asyncio實(shí)現(xiàn)異步操作

摘要:如何通過(guò)asyncio實(shí)現(xiàn)異步IO;用aiohttp模塊編寫(xiě)支持多用戶(hù)高并發(fā)的服務(wù)器。


*寫(xiě)在前面:為了更好的學(xué)習(xí)python,博主記錄下自己的學(xué)習(xí)路程。本學(xué)習(xí)筆記基于廖雪峰的Python教程,如有侵權(quán),請(qǐng)告知?jiǎng)h除。歡迎與博主一起學(xué)習(xí)Pythonヽ( ̄▽?zhuān)?? *


目錄

異步IO
asyncio
async/await
aiohttp
小結(jié)

異步IO

asyncio

asyncio是Python 3.4版本引入的標(biāo)準(zhǔn)庫(kù),直接內(nèi)置了對(duì)異步IO的支持。

我們只要從asyncio模塊中獲取一個(gè)EventLoop的引用,然后把需要執(zhí)行的協(xié)程放到EventLoop中執(zhí)行,就可以實(shí)現(xiàn)異步IO了。

我們看個(gè)簡(jiǎn)單的例子:

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()]                                 # 把兩個(gè)coroutine封裝起來(lái)
loop.run_until_complete(asyncio.wait(tasks))      # 把封裝好的coroutine放到EventLoop中執(zhí)行
loop.close()

語(yǔ)句@asyncio.coroutine是把緊接的generator標(biāo)記為協(xié)程coroutine類(lèi)型。

語(yǔ)句yield from可以調(diào)用另一個(gè)generator,并且拿取返回值(這里為None)。

語(yǔ)句asyncio.sleep(1)可以當(dāng)作是一個(gè)耗時(shí)1秒的IO操作。

定義好coroutine之后,把它們封裝好,放入EventLoop中執(zhí)行即可。

運(yùn)行結(jié)果:

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

可以看到coroutine A和coroutine B是并發(fā)執(zhí)行的。

如果把a(bǔ)syncio.sleep()換成真正的IO操作,就可以實(shí)現(xiàn)多個(gè)coroutine就由一個(gè)線(xiàn)程并發(fā)執(zhí)行的異步IO操作了。

我們用asyncio的異步網(wǎng)絡(luò)連接來(lái)獲取sina、sohu和163的網(wǎng)站首頁(yè)(例子源自廖雪峰官網(wǎng))

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

執(zhí)行結(jié)果:

wget www.sohu.com...
wget www.sina.com.cn...
wget www.163.com...
(等待一段時(shí)間)
(打印出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版本中,引入了新語(yǔ)法asyncawait,讓coroutine的代碼更簡(jiǎn)潔易讀。

其中:

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

await用于替換之前的yield from。

我們用更為簡(jiǎn)潔的語(yǔ)法把上面的代碼重新編寫(xiě)一下:

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

執(zhí)行結(jié)果與上面一致。

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

aiohttp

在上面的例子中,asyncio用在了客戶(hù)端上。

實(shí)際上asyncio多用在服務(wù)器端上,例如Web服務(wù)器。由于HTTP連接就是IO操作,通過(guò)asyncio可以實(shí)現(xiàn)多用戶(hù)的高并發(fā)支持。

aiohttp是基于asyncio實(shí)現(xiàn)的HTTP框架。

aiohttp沒(méi)有內(nèi)置,需要通過(guò)pip下載安裝:

pip install aiohttp

我們?cè)囈幌掠胊iohttp編寫(xiě)一個(gè)服務(wù)器,來(lái)處理下面兩個(gè)URL:

/:首頁(yè),返回b'<h1>Index</h1>';
/hello/{name}:根據(jù)URL參數(shù)返回文本hello, %s!

代碼如下 (源自廖雪峰官網(wǎng))

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)          # 創(chuàng)建TCP服務(wù)
    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()

運(yùn)行之后,在瀏覽器中輸入http://localhost:8000/hello/xxx,結(jié)果如下:

aiohttp.png

小結(jié)

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

異步操作需要在coroutine中通過(guò)yield from完成;

coroutine放到asyncio提供EventLoop引用中執(zhí)行,即可實(shí)現(xiàn)異步操作;

在Python3.5及之后的版本中,語(yǔ)法@asyncio.coroutine替換成async,語(yǔ)法yield from替換成await。

異步IO更多用于服務(wù)器端,通過(guò)aiohttp模塊,可以簡(jiǎn)單地編寫(xiě)出支持多用戶(hù)高并發(fā)的服務(wù)器。


以上就是本節(jié)的全部?jī)?nèi)容,感謝你的閱讀。

有任何問(wèn)題與想法,歡迎評(píng)論與吐槽。

和博主一起學(xué)習(xí)Python吧( ̄▽?zhuān)?~*

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容