Python | 非常適合小白的 Asyncio 教程

2021-03-02 鹹魚學Python

原作:adam1q84

原文:https://segmentfault.com/a/1190000008814676

Python | Python學習之多線程詳解

Python | Python學習之多進程詳解

Python | 進程 & 線程的理解拾遺

Python | 並行 & 並發拾遺

所謂「異步 IO」,就是你發起一個 IO 操作,卻不用等它結束,你可以繼續做其他事情,當它結束時,你會得到通知。Asyncio 是並發(concurrency)的一種方式。對 Python 來說,並發還可以通過線程(threading)和多進程(multiprocessing)來實現。

Asyncio 並不能帶來真正的並行(parallelism)。當然,因為 GIL(全局解釋器鎖)的存在,Python 的多線程也不能帶來真正的並行。可交給 asyncio 執行的任務,稱為協程(coroutine)。一個協程可以放棄執行,把機會讓給其它協程(即 yield from 或 await)。

定義協程

協程的定義,需要使用 async def 語句。

async def do_some_work(x): pass

do_some_work 便是一個協程。準確來說,do_some_work 是一個協程函數,可以通過 asyncio.iscoroutinefunction 來驗證:

print(asyncio.iscoroutinefunction(do_some_work)) # True

這個協程什麼都沒做,我們讓它睡眠幾秒,以模擬實際的工作量 :

async def do_some_work(x):    print("Waiting " + str(x))    await asyncio.sleep(x)

在解釋 await 之前,有必要說明一下協程可以做哪些事。協程可以:

* 等待一個 future 結束
* 等待另一個協程(產生一個結果,或引發一個異常)
* 產生一個結果給正在等它的協程
* 引發一個異常給正在等它的協程

asyncio.sleep 也是一個協程,所以 await asyncio.sleep(x) 就是等待另一個協程。可參見 asyncio.sleep 的文檔:

sleep(delay, result=None, *, loop=None)# Coroutine that completes after a given time (in seconds).

運行協程

調用協程函數,協程並不會開始運行,只是返回一個協程對象,可以asyncio.iscoroutine 來驗證:

print(asyncio.iscoroutine(do_some_work(3))) # True

此處還會引發一條警告:

async1.py:16: RuntimeWarning: coroutine 'do_some_work' was never awaited  print(asyncio.iscoroutine(do_some_work(3)))

要讓這個協程對象運行的話,有兩種方式:

* 在另一個已經運行的協程中用 `await` 等待它

* 通過 `ensure_future` 函數計劃它的執行

簡單來說,只有 loop 運行了,協程才可能運行。下面先拿到當前線程預設的 loop ,然後把協程對象交給 loop.run_until_complete,協程對象隨後會在 loop 裡得到運行。

loop = asyncio.get_event_loop()loop.run_until_complete(do_some_work(3))

run_until_complete 是一個阻塞(blocking)調用,直到協程運行結束,它才返回。這一點從函數名不難看出。run_until_complete 的參數是一個 future,但是我們這裡傳給它的卻是協程對象,之所以能這樣,是因為它在內部做了檢查,通過 ensure_future 函數把協程對象包裝(wrap)成了 future。所以,我們可以寫得更明顯一些:

loop.run_until_complete(asyncio.ensure_future(do_some_work(3)))

完整代碼:

import asyncio
async def do_some_work(x): print("Waiting " + str(x)) await asyncio.sleep(x)
loop = asyncio.get_event_loop()loop.run_until_complete(do_some_work(3))

運行結果:

回調

假如協程是一個 IO 的讀操作,等它讀完數據後,我們希望得到通知,以便下一步數據的處理。這一需求可以通過往 future 添加回調來實現。

def done_callback(futu):    print('Done')
futu = asyncio.ensure_future(do_some_work(3))futu.add_done_callback(done_callback)
loop.run_until_complete(futu)

多個協程

實際項目中,往往有多個協程,同時在一個 loop 裡運行。為了把多個協程交給 loop,需要藉助 asyncio.gather 函數。

loop.run_until_complete(asyncio.gather(do_some_work(1), do_some_work(3)))

或者先把協程存在列表裡:

coros = [do_some_work(1), do_some_work(3)]loop.run_until_complete(asyncio.gather(*coros))

運行結果:

Waiting 3Waiting 1<等待三秒鐘>Done

這兩個協程是並發運行的,所以等待的時間不是 1 + 3 = 4 秒,而是以耗時較長的那個協程為準。

參考函數 gather 的文檔:

gather(*coros_or_futures, loop=None, return_exceptions=False)
Return a future aggregating results from the given coroutines or futures.

發現也可以傳 futures 給它:

futus = [asyncio.ensure_future(do_some_work(1)),             asyncio.ensure_future(do_some_work(3))]
loop.run_until_complete(asyncio.gather(*futus))

gather 起聚合作用,把多個 futures 包裝成單個 future,因為 loop.run_until_complete 只接受單個 future。

run_until_complete 和 run_forever

我們一直通過 run_until_complete 來運行 loop ,等到 future 完成, run_until_complete 也就返回了。

async def do_some_work(x):    print('Waiting ' + str(x))    await asyncio.sleep(x)    print('Done')
loop = asyncio.get_event_loop()
coro = do_some_work(3)loop.run_until_complete(coro)

輸出:

Waiting 3
<等待三秒鐘>
Done
<程序退出>

現在改用 run_forever:

async def do_some_work(x):    print('Waiting ' + str(x))    await asyncio.sleep(x)    print('Done')
loop = asyncio.get_event_loop()
coro = do_some_work(3)asyncio.ensure_future(coro)
loop.run_forever()

輸出:

Waiting 3<等待三秒鐘>Done<程序沒有退出>

三秒鐘過後,future 結束,但是程序並不會退出。run_forever 會一直運行,直到 stop 被調用,但是你不能像下面這樣調 stop:

loop.run_forever()loop.stop()

run_forever 不返回,stop 永遠也不會被調用。所以,只能在協程中調 stop:

async def do_some_work(loop, x):    print('Waiting ' + str(x))    await asyncio.sleep(x)    print('Done')    loop.stop()

這樣並非沒有問題,假如有多個協程在 loop 裡運行:

asyncio.ensure_future(do_some_work(loop, 1))asyncio.ensure_future(do_some_work(loop, 3))
loop.run_forever()

第二個協程沒結束,loop 就停止了——被先結束的那個協程給停掉的。要解決這個問題,可以用 gather 把多個協程合併成一個 future,並添加回調,然後在回調裡再去停止 loop。

async def do_some_work(loop, x):    print('Waiting ' + str(x))    await asyncio.sleep(x)    print('Done')
def done_callback(loop, futu): loop.stop()
loop = asyncio.get_event_loop()
futus = asyncio.gather(do_some_work(loop, 1), do_some_work(loop, 3))futus.add_done_callback(functools.partial(done_callback, loop))
loop.run_forever()

其實這基本上就是 run_until_complete 的實現了,run_until_complete 在內部也是調用 run_forever。

Close Loop?

以上示例都沒有調用 loop.close,好像也沒有什麼問題。所以到底要不要調 loop.close 呢?
簡單來說,loop 只要不關閉,就還可以再運行。:

loop.run_until_complete(do_some_work(loop, 1))loop.run_until_complete(do_some_work(loop, 3))loop.close()

但是如果關閉了,就不能再運行了:

loop.run_until_complete(do_some_work(loop, 1))loop.close()loop.run_until_complete(do_some_work(loop, 3))  # 此處異常

建議調用 loop.close,以徹底清理 loop 對象防止誤用。

gather vs. wait

asyncio.gather 和 asyncio.wait 功能相似。

coros = [do_some_work(loop, 1), do_some_work(loop, 3)]loop.run_until_complete(asyncio.wait(coros))

具體差別可請參見 StackOverflow 的討論:Asyncio.gather vs asyncio.wait。

Timer

C++ Boost.Asio 提供了 IO 對象 timer,但是 Python 並沒有原生支持 timer,不過可以用 asyncio.sleep 模擬。

async def timer(x, cb):    futu = asyncio.ensure_future(asyncio.sleep(x))    futu.add_done_callback(cb)    await futu
t = timer(3, lambda futu: print('Done'))loop.run_until_complete(t)

Love&Share 

[ 完 ]

對了,看完記得一鍵四連,這個對我真的很重要。

相關焦點

  • 非常適合小白的 Asyncio 教程
    協程可以:等待一個 future 結束等待另一個協程(產生一個結果,或引發一個異常)產生一個結果給正在等它的協程引發一個異常給正在等它的協程asyncio.sleep 也是一個協程,所以 await asyncio.sleep(x) 就是等待另一個協程。
  • Python裡最難的Asyncio,這裡有一份非常適合小白的教程
    協程可以:等待一個 future 結束等待另一個協程(產生一個結果,或引發一個異常)產生一個結果給正在等它的協程引發一個異常給正在等它的協程asyncio.sleep 也是一個協程,所以 await asyncio.sleep(x) 就是等待另一個協程。
  • python : 利用 asyncio 進行快速抓取
    (點擊上方藍字,快速關注我們)來源:伯樂在線專欄作者 - 慕容老匹夫如有好文章投稿,請點擊 → 這裡了解詳情如需轉載,發送「轉載」二字查看說明web數據抓取是一個經常在python我喜歡DIY的原因在於其靈活性,但是卻不適合用來做大量數據的抓取,因為需要請求同步,所以大量的請求意味著你不得不等待很長時間。在本文中,我將會為你展示一個基於新的異步庫(aiohttp)的請求的代替品。我使用它寫了一些速度的確很快的小數據抓取器,下面我將會為你演示是如何做到的。
  • Python中asyncio神器的入門
    asyncioasyncio是Python 3.4版本引入的標準庫,直接內置了對異步IO的支持。不僅有可以用作後端異步編程aiohttp,也可在爬蟲中發揮出異步的優勢的asyncio,甚至可以比Scrapy還要快,這種神器,其實也並不難理解。從Python 3.5開始引入了新的語法async和await,讓協程的代碼更簡潔易讀。
  • Python 協程模塊 asyncio 使用指南
    我們用到是 Python 標準庫的 asyncio 模塊。asyncio 是異步教程中必須學習的基礎模塊。協程的好處就是單線程即可輕鬆實現百萬級的並發,同時速度上也得到大大提升,在爬蟲領域和 Web 開發領域等都有很大的用途,但是,這些模塊都是要配合協程模塊 asyncio 使用的。
  • Python異步編程模塊asyncio學習
    同時asyncio也支持調度代碼在將來的某個特定事件運行,從而支持一個協程等待另一個協程完成,以處理系統信號和識別其他一些事件。Python異步編程模塊asyncio學習作者:cxapython | 來源:Python學習開發異步並發的概念對於其他的並發模型大多數採取的都是線性的方式編寫。
  • python異步編程模塊asyncio學習(二)
    在上次我們說如何使用asyncio,詳情點擊:python異步編程模塊asyncio學習(一)。接下來我們繼續學習關於異步模塊asyncio的其他操作。儘管asyncio應用通常作為單線程運行,不過仍被構建為並發應用。由於I/O以及其他外部事件的延遲和中斷,每個協程或任務可能按一種不可預知的順序執行。
  • Python基礎 | 大學小白如何入門Python程序設計
    一、 問題闡述對於剛剛進入大學小白的我們對許許多多課程感到陌生,例如高數、大學計算機網絡、Python語言程序設計等一些課程對於我們剛剛進入大學小白的我們很多時間就聽不懂老師在講什麼,大學和高中完全是不一樣的,大學更多的時間是需要自己去自學,僅僅靠老師上課講的那一點時間是完全不夠的,更多的需要自己課後的練習
  • python之asyncio包處理並發2
    #適合asyncio API 的協程在定義體中必須使用yield from,而不能使用yield。
  • Python async/await教程
    Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。
  • 書聲琅琅:好的Python入門教程
    好的Python入門教程,書聲琅琅教育番茄老師微信pykf20介紹,python語言現在應用非常廣泛,不管是大數據還是人工智慧,應用最多的語言還是python,因此對於許多小白來講,看到python從業者的高薪資,想要轉行,或者致力於python開發的朋友,如果要學習python,從零基礎開始,一定需要一套完整的學習路線。
  • 小白使用VS code編寫python,如何優雅避坑
    01 寫這篇文章的初衷接下來,「小白學記」會陸續推出python的學習歷程。當然,寫這篇文章的時候,小編在python方面仍然是一個不折不扣的小白,知識掌握的還相當有限。當寫到這篇文章的時候,小編也愈發感覺到做「小白學記」的意義。拿這次要講的VS code編譯器來說,我們在網上隨便一查就可以找到很多資源,它們會給出很多詳細的教程。
  • python教程
    python視頻教程     文章底部留言 序號 給您發送視頻教程連結或者加微信 bigzql 索要Python
  • 小白的自學Python線路
    開發工具pycharmPycharm是最好的Python代碼編輯工具之一,學習成本非常底,下載安裝後可直接上手。下載地址:https://jupyter.org/install自學網站菜鳥網站的這個python教程很適合小白學習,沒有高深的原理,照著教程學就完事了。
  • python之asyncio包處理並發5
    #改進asyncio下載腳本 展示如何使用100 個並發請求(-m 100)從ERROR 伺服器中下載100 面國旗(-al 100)。運行flags2_asyncio.py 腳本#重寫後的函數可以供concurrent.future 版重用。
  • Python與協程從Python2—Python3
    所以很適合用於高並發處理。Python對協程的支持還非常有限,用在generator中的yield可以一定程度上實現協程。雖然支持不完全,但已經可以發揮相當大的威力了。gevent模塊Python通過yield提供了對協程的基本支持,但是不完全。
  • 一日一技:向Python中添加延遲函數time.sleep()
    在本教程中,我們將學習:什麼是Python Sleep?示例:在Python中使用sleep()函數請按照以下給出的步驟在您的python腳本中添加sleep()。函數我們可以在python 3.4及更高版本中使用asyncio.sleep。
  • Python協程:概念及其用法
    Python2.x協程python2.x協程應用:python2.x中支持協程的模塊不多,gevent算是比較常用的,這裡就簡單介紹一下gevent的用法。由於IO操作非常耗時,經常使程序處於等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在運行,而不是等待IO。
  • Python安裝教程之anaconda篇
    【導讀】我們知道,Python的功能非常強大。那麼對於迫切想學習Python的新手同學來說,第一件事情可能需要了解python是什麼?能用來做什麼?語法結構是怎樣的?這些我們幾句話很難介紹清楚,後續會陸續出python入門教程來為大家一一介紹。為了方便了解python是什麼,我想首先把python安裝到自己的電腦中也是很重要的步驟。本文將手把手教你如何安裝python.
  • Python異步IO實現全過程
    在開始之前,請先確保你已經安裝了本教程所需的 asyncio 和一些其他的庫。環境配置為全面的理解本文,你需要安裝Python3.7及以上版本,還有 aiohttp 和 aiofiles 包:多進程非常擅長處理計算密集型(CPU-bound)任務:強密集循環和數學計算都屬於此類。並發則比並行略微廣泛一些。它表示多個任務可以重疊運行。(還有句話說並發並不意味著並行。)線程是一種多個線程輪流執行任務的並發執行模型。一個進程中可以有多個線程。由於GIL的存在,Python與線程有著複雜的關係,但這超出了本文的範圍。