第十七章-異步IO

異步IO的出現源自於CPU速度與IO速度徹底不匹配python

通常的能夠採用多線程或者多進程的方式來解決IO等待的問題web

一樣異步IO也能夠解決同步IO所帶來的問題服務器

常見的異步IO的實現方式是使用一個消息循環, 主線程不斷的讀取這個消息循環以便肯定IO操做是否完成多線程

1 協程

  協程(微線程, 纖程)併發

  通常子程序調用是一個入口一個出口, 調用的順序也是明確的app

  可是協程不一樣, 執行過程當中子程序內部中斷, 就會轉而執行別的子程序, 等待合適的時間返回 , 這樣的行爲相似於進程的切換 webapp

  正由於協程又相似於線程執行的特性, 可是子程序切換幾乎沒有切換開銷, 所以性能很好異步

  並且協程還能夠避免臨界資源操做的問題async

  協程是一個線程執行函數

  構造協程通常是使用生成器, 利用send()發送相互傳遞數據

  常見的生產者消費者的範例

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

2 asyncio

  基本使用爲

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裝飾一個執行函數, 這樣即可以生成一個異步執行IO的函數

  使用asyncio.get_event_loop()建立一個循環

  經過這個循環的對象執行run_until_complete()來執行異步IO

    其中run_until_complete()能夠傳入一個函數執行

    若是須要多個函數的話, 就須要將這些函數執行方法一個list中, 並使用asyncid.wait(list)

tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))

  最後調用close()結束異步IO

  其中函數的寫法是 須要使用 yield from來實現異步IO

  獲取網頁的函數編寫以下

@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()))
    writer.close()

3 async和await

  使用asyncio.coroutine能夠吧一個生成器標記爲coroutine類型

  而後在內部使用yield from調用另外一個coroutine實現異步操做

  爲了進一步簡化操做, 出現了async和await, 這是在python3.5開始支持的語法

  簡化以下

    1) 裝飾器asyncio.corontine替換爲async

    2) yield from替換爲await

  所以上述代碼能夠簡化爲

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

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

4 aiohttp

  asyncio實現了單線程併發io操做, 若是隻是應用於客戶端, 那麼做用還不是那麼明顯

  針對於服務器, asyncio可能發揮更好的效果, 基於http協議實現的asyncio就是aiohttp

  安裝

pip install aiohttp

  1) 導入包

import asyncio
from aiohttp import web

  2) 實現mian代碼

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(init(loop))
    loop.run_forever()

  3) 編寫初始化函數

  將循環loop做爲參數傳入, 用於建立webapp

  添加路由, 並指定處理函數

  最後建立服務器, 利用create_server()建立TCP服務

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

  4) 編寫兩個路由的處理函數

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'))
相關文章
相關標籤/搜索