異步框架之深入理解asyncio(一)

前言

  • 這幾天看asyncio相關的pycon視頻又重溫了asyncio 的官方文檔,收獲很多。之前asyncio被吐槽的一點就是文檔寫的不好,Python 3.7 時 asyncio 的官方文檔被 Andrew Svetlov 以及 Yury Selivanov 等核心開發者重寫了,新的版本我覺得已經好很多了。借著這篇筆記記錄一下我對asyncio的一些理解。

核心概念

asyncio里面主要有4個需要關注的基本概念

  • Eventloop

  • Eventloop可以說是asyncio應用的核心,是中央總控。Eventloop實例提供了注冊、取消和執行任務和回調的方法。

  • 把一些異步函數(就是任務,Task,一會就會說到)注冊到這個事件循環上,事件循環會循環執行這些函數(但同時只能執行一個),當執行到某個函數時,如果它正在等待I/O返回,事件循環會暫停它的執行去執行其他的函數;當某個函數完成I/O后會恢復,下次循環到它的時候繼續執行。因此,這些異步函數可以協同(Cooperative)運行:這就是事件循環的目標

Coroutine

  • 協程(Coroutine)本質上是一個函數,特點是在代碼塊中可以將執行權交給其他協程:


    在這里插入圖片描述

    這里面有4個重要關鍵點:

  • 協程要用 asyncdef聲明,Python 3.5時的裝飾器寫法已經過時,我就不列出來了。

  • asyncio.gather用來并發運行任務,在這里表示協同的執行a和b2個協程

  • 在協程a中,有一句 awaitasyncio.sleep(0),await表示調用協程,sleep 0并不會真的sleep(因為時間為0),但是卻可以把控制權交出去了。

  • asyncio.run是Python 3.7新加的接口,要不然你得這么寫:

  • 在這里插入圖片描述

    在這里插入圖片描述

    看到了吧,在并發執行中,協程a被掛起又恢復過。

Future

接著說Future,它代表了一個「未來」對象,異步操作結束后會把最終結果設置到這個Future對象上。Future是對協程的封裝,不過日常開發基本是不需要直接用這個底層Future類的。我在這里只是演示一下:

在這里插入圖片描述

可以對這個Future實例添加完成后的回調(add_done_callback)、取消任務(cancel)、設置最終結果(set_result)、設置異常(如果有的話,set_exception)等。現在我們讓Future完成:
在這里插入圖片描述

看到了吧,await之后狀態成了finished。這里順便說一下,一個對象怎么樣就可以被await(或者說怎么樣就成了一個awaitable對象)呢?給類實現一個await方法,Python版本的Future的實現大概如下:
在這里插入圖片描述

這樣就可以 awaitfuture了,那為什么 awaitfuture后Future的狀態就能改變呢,這是因為用 loop.run_in_executor創建的Future注冊了一個回調(通過 asyncio.futures.wrap_future,加了一個 _call_set_state回調, 有興趣的可以通過延伸閱讀鏈接2找上下文)。

await里面的 yieldself不要奇怪,主要是為了兼容 iter,給舊的 yieldfrom用:

在這里插入圖片描述

Task

Eventloop除了支持協程,還支持注冊Future和Task2種類型的對象,那為什么要存在Future和Task這2種類型呢?

先回憶前面的例子,Future是協程的封裝,Future對象提供了很多任務方法(如完成后的回調、取消、設置任務結果等等),但是開發者并不需要直接操作Future這種底層對象,而是用Future的子類Task協同的調度協程以實現并發。

Task非常容易創建和使用:


在這里插入圖片描述

asyncio并發的正確/錯誤姿勢

在代碼中使用async/await是不是就能發揮asyncio的并發優勢么,其實是不對的,我們先看個例子:
有2個協程a和b,分別sleep1秒和3秒,如果協程可以并發執行,那么執行時間應該是sleep最大的那個值(3秒),現在它們都在s1協程里面被調用。大家先猜一下s1會運行幾秒?

我們寫個小程序驗證一下:


在這里插入圖片描述

大家注意我這個時間計數用的方法,沒有用time.time,而是用了Python 3.3新增的time.perf_counter它是現在推薦的用法。我們在IPython里面驗證下:


在這里插入圖片描述

看到了吧,4秒!!!,相當于串行的執行了(sleep 3 + 1)。這是錯誤的用法,應該怎么用呢,前面的asyncio.gather就可以:
在這里插入圖片描述

看到了吧,3秒!另外一個是asyncio.wait:


在這里插入圖片描述

同樣是3秒。先別著急,gather和wait下篇文章還會繼續對比。還有一個方案就是用asyncio.create_task
在這里插入圖片描述

都是3秒。asyncio.create_task相當于把協程封裝成Task。不過大家要注意一個錯誤的用法:
在這里插入圖片描述

直接await task不會對并發有幫助*。asyncio.createtask是Python 3.7新增的高階API,是推薦的用法,其實你還可以用asyncio.ensure_future和loop.createtask:
在這里插入圖片描述

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

推薦閱讀更多精彩內容