Python并發之異步I/O(async,await)

Python并發之異步I/O(async,await)

背景

Python有很長一段的異步編程歷史,特別是twistedgevent和一些無堆棧的Python項目。
異步編程因為一些好的原因在這些年來越來越受關注。盡管相對于傳統的線性風格更難一點,但是卻是值得的:因為異步編程有更高的效率。
舉個例子:在Http請求方面,Python異步協程可以提交請求然后去做隊列中其他等待的任務,讓它慢慢請求,而不是傳統的一直等它請求到完成為止,這樣的話會浪費更多的時間與資源。總之異步編程能讓你的代碼在處于等待資源狀態時處理其他任務。
在Python3.4中,asyncio產生了。而在Python3.5中,有加入了對async defawait新的語法支持,讓我們看一看它們怎么工作的。

協程

在Python中,一個異步的函數我們通常叫它協程。之前我們在講解yield的時候也已經講過yield語法在協程中的基本使用了,這次同樣是協程,但卻是不同的語法。

#在Python 3.4中, 創建一個協程我們用asyncio.coroutine裝飾器:
async def double(x):
    return x * 2

# 這是個協程對象
>>> double(6)
>>><coroutine object double at 0x115b59d58>

# 既然是協程,我們像之前yield協程那樣,預激活一下(注意這里用next(double(6)預激活會報錯)
>>> double(6).send(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: 12
# 好像差不多。

或者是這樣的:

async def ping_server(ip):
    # ping code here...

# 用await語法代替"yield from"
async def ping_local():  
    return await ping_server('192.168.1.1')

建議使用Python3.5最新主流語法:async def ,await

asyncio的幾個重要結構

我這里都用英文展示,原生,易理解。怎么翻譯都繞口。

event loop:

An event loop essentially manages and distributes the execution of different tasks. It registers them and handles distributing the flow of control between them.

Coroutines:

Coroutines are special functions that work similarly Python generators that on await they release the flow of control back to the event loop. A coroutine needs to be scheduled to run using the event loop, to do this we create a Task, which is a type of Future.

Futures:

Futures are objects that represent the result of a task that may or may not have been executed. This result may be an exception.

理解基本架構很重要,理解協程,future,task的概念重中之重,至于什么內部實現,真的挺復雜的,無需關心。

在這邊的時候我卡了很長時間:futureTask的區別是什么????

future在多線程說過,future表示終將發生的事情,而確定某件事會發生的唯一方式是執行的時間已經排定。(你不排定它,它就是個協程),那怎么排定?

BaseEventLoop.create_task(...) 或者 asyncio.ensure_future方法接收一個協程,排定它的運行時間,然后返回一個asyncio.Task 實例——也是 asyncio.Future 類的實例,因為 Task 是Future 的子類,用于包裝協程。這與調用 Executor.submit(...) 方法創建Future實例是一個道理

這句話我讀了好多遍,意思是不是說future跟task是同一樣東西。對于event loop來說,一個包裝了協程的future,就是循環中的一個task?我是這么理解的。

我們無法確定future啥時完成結束,但是總歸結束(無論報錯還是返回值)的,因為我們已經給它排定了時間。

例子1

少廢話,直接看例子。

import asyncio
import time

start = time.time()


def tic():
    return 'at %1.1f seconds' % (time.time() - start)


async def gr1():
    # Busy waits for a second, but we don't want to stick around...
    print('gr1 started work: {}'.format(tic()))
    # 暫停兩秒,但不阻塞時間循環,下同
    await asyncio.sleep(2)
    print('gr1 ended work: {}'.format(tic()))


async def gr2():
    # Busy waits for a second, but we don't want to stick around...
    print('gr2 started work: {}'.format(tic()))
    await asyncio.sleep(2)
    print('gr2 Ended work: {}'.format(tic()))


async def gr3():
    print("Let's do some stuff while the coroutines are blocked, {}".format(tic()))
    await asyncio.sleep(1)
    print("Done!")

# 事件循環
ioloop = asyncio.get_event_loop()

# tasks中也可以使用asyncio.ensure_future(gr1())..
tasks = [
    ioloop.create_task(gr1()),
    ioloop.create_task(gr2()),
    ioloop.create_task(gr3())
]
ioloop.run_until_complete(asyncio.wait(tasks))
ioloop.close()


output:
    gr1 started work: at 0.0 seconds
    gr2 started work: at 0.0 seconds
    Let's do some stuff while the coroutines are blocked, at 0.0 seconds
    Done!
    gr2 Ended work: at 2.0 seconds
    gr1 ended work: at 2.0 seconds
  • asyncio.wait(...) 協程的參數是一個由future或協程構成的可迭代對象;wait 會分別
    把各個協程包裝進一個 Task 對象。最終的結果是,wait 處理的所有對象都通過某種方式變成 Future 類的實例。wait 是協程函數,因此返回的是一個協程或生成器對象。

  • ioloop.run_until_complete 方法的參數是一個future或協程。如果是協程,run_until_complete方法與 wait 函數一樣,把協程包裝進一個 Task 對象中。

  • 在 asyncio 包中,future和協程關系緊密,因為可以使用 yield from 從asyncio.Future 對象中產出結果。這意味著,如果 foo 是協程函數(調用后返回協程對象),抑或是返回Future 或 Task 實例的普通函數,那么可以這樣寫:res = yield from foo()。這是 asyncio 包的 API 中很多地方可以互換協程與期物的原因之一。 例如上面的例子中tasks可以改寫成協程列表:tasks = [gr1(), gr(2), gr(3)]

詳細的各個類說明,類方法,傳參,以及方法返回的是什么類型都可以在官方文檔上仔細研讀,多讀幾遍,方有體會。

好好體會event loop中運作圖.....如下所示:


async.jpg

例子2

例子1只是用來講述語法,實際用途中還是得看這個例子。上節多線程與多進程的例子,獲取有效網址。

import asyncio
import time
import aiohttp
import async_timeout

msg = "http://www.nationalgeographic.com.cn/photography/photo_of_the_day/{}.html"
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
}

urls = [msg.format(i) for i in range(4500, 5057)]

async def fetch(session, url):
    with async_timeout.timeout(10):
        async with session.get(url) as response:
            return response.status

async def main(url):
    async with aiohttp.ClientSession() as session:
            status = await fetch(session, url)
            return status

if __name__ == '__main__':
    start = time.time()
    loop = asyncio.get_event_loop()
    tasks = [main(url) for url in urls]
    # 返回一個列表,內容為各個tasks的返回值
    status_list = loop.run_until_complete(asyncio.gather(*tasks))
    print(len([status for status in status_list if status==200]))
    end = time.time()
    print("cost time:", end - start)

任重而道遠

  • 在封閉的代碼塊中使用一些新的關鍵字就能實現異步功能
  • 我對于這一塊還是處于小白狀態,掌握不是很全面
  • 多Google,多Google,多Google.....

參考資料

http://stackabuse.com/python-async-await-tutorial/
https://hackernoon.com/asyncio-for-the-working-python-developer-5c468e6e2e8e
https://docs.python.org/3/library/asyncio-task.html
http://quietlyamused.org/blog/2015/10/02/async-python/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容