Python--異步編程

同步和異步

  • 同步:調用一個函數,返回結果是自己去獲取的,不管是阻塞還是非阻塞
  • 異步:調用一個函數,返回結果是別人通過通知、回調等方式給你的

在IO操作的過程中,當前線程被掛起,而其他需要CPU執行的代碼就無法被當前線程執行了。多線程和多進程雖然解決了併發問題,但是系統不能無上限的增加線程,由於系統切換線程的開銷也很大,所以,一旦線程數量過多,CPU的時間就花在線程切換上了,會導致性能下降。

爲了解決CPU和IO設備的速度嚴重不匹配的問題,多線程和多進程只是解決這問題的一種方法。另一種方法是異步IO。當代碼需要執行一個耗時的IO操作時,它只發出IO指令,並不等待IO結果,然後就去執行其他代碼了,一段時間後,當IO返回結果時,再通知CPU進行處理。

協程

瞭解異步IO之前,我們先來了解協程,協程,又稱微線程。

Python對協程的支持是通過生成器實現的,在生成器中,我們不但可以通過for循環來迭代,還可以不斷調用next()函數獲取由yield語句返回的下一個值。但是Python的yield不但可以返回一個值,它還可以接收調用者發出的參數。

傳統的生產者-消費者模型是一個線程寫消息,一個線程取消息,通過鎖機制控制隊列和等待,但一不小心就可能死鎖。

如果改用協程,生產者生產消息後,直接通過yield跳轉到消費者開始執行,待消費者執行完畢後,切回到生產者繼續生產,效率極高

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

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

c = consumer()
produce(c)

輸出:

Producing 1...
Consuming 1...
Consumer return: 200 OK
Producing 2...
Consuming 2...
Consumer return: 200 OK
Producing 3...
Consuming 3...
Consumer return: 200 OK
Producing 4...
Consuming 4...
Consumer return: 200 OK
Producing 5...
Consuming 5...
Consumer return: 200 OK

注意到consumer函數是一個生成器,把consumer傳入produce後:

  • 首先調用c.send(None)啓動生成器;
  • 然後,一旦生產了東西,通過c.send(n)切換到comsumer執行;
  • consumer通過yield拿到消息,處理,又通過yield把結果傳回;
  • produce拿到consumer處理的結果,繼續生產下一條消息;
  • produce決定不生產了,通過c.close()關閉consumer,整個過程結束。

整個流程無鎖,由一個線程執行,produce和consumer協作完成任務,所以稱爲協程,而非線程的搶佔式多任務。

異步IO

asyncio是Python3.4版本引入的標準庫,直接內置了對異步IO的支持。

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

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

@asyncio.coroutine就是把hello這個生成器標記爲協程,然後,我們就把這個協程扔到事件循環中執行。

輸出:

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

由輸出信息可以看出,兩個協程是由同一個線程併發執行的,如果把asyncio.sleep()換成真正的IO操作,則多個協程就可以由一個線程併發執行。

async/await

用asyncio提供的asyncio.coroutine可以把一個生成器標記爲協程類型,然後在協程內部用yield from調用另外一個協程,實現異步操作。

爲了簡化並更好的標識異步IO,從Python3.5開始引入了新的語法async和await。

async和await是針對協程的新語法,要使用新的語法,只需要做兩步簡單的替換:

  • 把@asyncio.coroutine替換爲async;
  • 把yield from替換爲await.

用新語法重寫上面的列子:

async def hello():
    print('Hello world! (%s)' % threading.currentThread())
    await asyncio.sleep(1)
    print('Hello again! (%s)' % threading.currentThread())

其他的代碼保持不變

aiohttp

asyncio可以實現單線程併發IO操作。如果僅用在客戶端,發揮的威力不大,如果把asyncio用在服務端,例如Web服務器,由於HTTP連接就是IO操作,因此可以使用單線程+協程實現多用戶的高併發支持。

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

客戶端舉例:

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'http://python.org')
        print(html)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

服務端舉例:

from aiohttp import web

async def handle(request):
    name = request.match_info.get('name', "Anonymous")
    text = "Hello, " + name
    return web.Response(text=text)

app = web.Application()
app.add_routes([web.get('/', handle),
                web.get('/{name}', handle)])

web.run_app(app)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章