Python 中的非同步程式設計:Asyncio
如果你已經決定要理解 Python 的非同步部分,歡迎來到我們的“Asyncio How-to ”。
注:哪怕連異動正規化的存在都不知道的情況下,你也可以成功地使用 Python。但是,如果你對底層執行模式感興趣的話,asyncio 絕對值得檢視。
非同步是怎麼一回事?
在傳統的順序程式設計中, 所有傳送給直譯器的指令會一條條被執行。此類程式碼的輸出容易顯現和預測。 但是…
譬如說你有一個指令碼向3個不同伺服器請求資料。 有時,誰知什麼原因,傳送給其中一個伺服器的請求可能意外地執行了很長時間。想象一下從第二個伺服器獲取資料用了10秒鐘。在你等待的時候,整個指令碼實際上什麼也沒幹。如果你可以寫一個指令碼可以不去等待第二個請求而是僅僅跳過它,然後開始執行第三個請求,然後回到第二個請求,執行之前離開的位置會怎麼樣呢。就是這樣。你通過切換任務最小化了空轉時間。儘管如此,當你需要一個幾乎沒有I/O的簡單指令碼時,你不想用非同步程式碼。
還有一件重要的事情要提,所有程式碼在一個執行緒中執行。所以如果你想讓程式的一部分在後臺執行同時幹一些其他事情,那是不可能的。
準備開始
這是 asyncio 主概念最基本的定義:
- 協程 — 消費資料的生成器,但是不生成資料。Python 2.5 介紹了一種新的語法讓傳送資料到生成器成為可能。我推薦查閱David Beazley “A Curious Course on Coroutines and Concurrency” 關於協程的詳細介紹。
- 任務 — 協程排程器。如果你觀察下面的程式碼,你會發現它只是讓 event_loop 儘快呼叫它的_step ,同時 _step 只是呼叫協程的下一步。
12345678910111213141516171819 | class Task(futures.Future): def __init__(self, coro, loop=None): super().__init__(loop=loop) ... self._loop.call_soon(self._step) def _step(self): ... try: ... result = next(self._coro) except StopIteration as exc: self.set_result(exc.value) except BaseException as exc: self.set_exception(exc) raise else: ... self._loop.call_soon(self._step) |
- 事件迴圈 — 把它想成 asyncio 的中心執行器。
現在我們看一下所有這些如何融為一體。正如我之前提到的,非同步程式碼在一個執行緒中執行。
從上圖可知:
1.訊息迴圈是線上程中執行
2.從佇列中取得任務
3.每個任務在協程中執行下一步動作
4.如果在一個協程中呼叫另一個協程(await <coroutine_name>),會觸發上下文切換,掛起當前協程,並儲存現場環境(變數,狀態),然後載入被呼叫協程
5.如果協程的執行到阻塞部分(阻塞I/O,Sleep),當前協程會掛起,並將控制權返回到執行緒的訊息迴圈中,然後訊息迴圈繼續從佇列中執行下一個任務...以此類推
6.佇列中的所有任務執行完畢後,訊息迴圈返回第一個任務
非同步和同步的程式碼對比
現在我們實際驗證非同步模式的切實有效,我會比較兩段 python 指令碼,這兩個指令碼除了sleep 方法外,其餘部分完全相同。在第一個腳本里,我會用標準的 time.sleep 方法,在第二個腳本里使用 asyncio.sleep 的非同步方法。
這裡使用 Sleep 是因為它是一個用來展示非同步方法如何操作 I/O 的最簡單辦法。
使用同步 sleep 方法的程式碼:
123456789101112131415161718192021222324252627282930 | import asyncio import time from datetime import datetimeasync def custom_sleep(): print('SLEEP', datetime.now()) time.sleep(1)async def factorial(name, number): f = 1 for i in range(2, number+1): print('Task {}: Compute factorial({})'.format(name, i)) await custom_sleep() f *= i print('Task {}: factorial({}) is {}\n'.format(name, number, f))start = time.time() loop = asyncio.get_event_loop()tasks = [ asyncio.ensure_future(factorial("A", 3)), asyncio.ensure_future(factorial("B", 4)),]loop.run_until_complete(asyncio.wait(tasks)) loop.close()end = time.time() print("Total time: {}".format(end - start)) |
指令碼輸出:
123456789101112131415 | Task A: Compute factorial(2) SLEEP 2017-04-06 13:39:56.207479 Task A: Compute factorial(3) SLEEP 2017-04-06 13:39:57.210128 Task A: factorial(3) is 6Task B: Compute factorial(2) SLEEP 2017-04-06 13:39:58.210778 Task B: Compute factorial(3) SLEEP 2017-04-06 13:39:59.212510 Task B: Compute factorial(4) SLEEP 2017-04-06 13:40:00.217308 Task B: factorial(4) is 24Total time: 5.016386032104492 |
使用非同步 Sleep 的程式碼:
123456789101112131415161718192021222324252627282930 | import asyncio import time from datetime import datetimeasync def custom_sleep(): print('SLEEP {}\n'.format(datetime.now())) await asyncio.sleep(1)async def factorial(name, number): f = 1 for i in range(2, number+1): print('Task {}: Compute factorial({})'.format(name, i)) await custom_sleep() f *= i print('Task {}: factorial({}) is {}\n'.format(name, number, f))start = time.time() loop = asyncio.get_event_loop()tasks = [ asyncio.ensure_future(factorial("A", 3)), asyncio.ensure_future(factorial("B", 4)),]loop.run_until_complete(asyncio.wait(tasks)) loop.close()end = time.time() print("Total time: {}".format(end - start)) |
指令碼輸出:
1234567891011121314151617181920 | Task A: Compute factorial(2) SLEEP 2017-04-06 13:44:40.648665Task B: Compute factorial(2) SLEEP 2017-04-06 13:44:40.648859Task A: Compute factorial(3) SLEEP 2017-04-06 13:44:41.649564Task B: Compute factorial(3) SLEEP 2017-04-06 13:44:41.649943Task A: factorial(3) is 6Task B: Compute factorial(4) SLEEP 2017-04-06 13:44:42.651755Task B: factorial(4) is 24Total time: 3.008226156234741 |
從輸出可以看到,非同步模式的程式碼執行速度快了大概兩秒。當使用非同步模式的時候(每次呼叫 await asyncio.sleep(1) ),程序控制權會返回到主程式的訊息迴圈裡,並開始執行佇列的其他任務(任務A或者任務B)。
當使用標準的 sleep方法時,當前執行緒會掛起等待。什麼也不會做。實際上,標準的 sleep 過程中,當前執行緒也會返回一個 python 的直譯器,可以操作現有的其他執行緒,但這是另一個話題了。
推薦使用非同步模式程式設計的幾個理由
很多公司的產品都廣泛的使用了非同步模式,如 Facebook 旗下著名的 React Native 和 RocksDB 。像 Twitter 每天可以承載 50 億的使用者訪問,靠的也是非同步模式程式設計。所以說,通過程式碼重構,或者改變模式方法,就能讓系統工作的更快,為什麼不去試一下呢?
相關推薦
Python 中的非同步程式設計:Asyncio
如果你已經決定要理解 Python 的非同步部分,歡迎來到我們的“Asyncio How-to ”。 注:哪怕連異動正規化的存在都不知道的情況下,你也可以成功地使用 Python。但是,如果你對底層執行模式感興趣的話,asyncio 絕對值得檢視。 非同步是怎麼一回事? 在傳統
python 面向物件程式設計:類和例項
深度學習在構建網路模型時,看到用類來構建一個模型例項,清晰明瞭,所以這篇博文主要學習一下python類 類和例項: 類可以起到模板的作用,因此,可以在建立例項的時候,把一些我們認為必須繫結的屬性強制填寫進去。通過定義一個特殊的__init__(注意:特殊方法“__init__”前後分別有
jQuery中非同步問題:資料傳遞
最近寫一個新頁面,涉及到非同步問題,為了獲得非同步過程中的資料,以下分享兩種方法; 兩種方法一句話總結: 方法一,Http請求後呼叫.then實現response的資料同步,然後根據resp接著處理; 方法二,使用ES6中的Promise語法糖,實現非同步等待resp; 方法一的程式碼
Python 的非同步 IO:Asyncio 簡介(一)
Python 的 asyncio 類似於 C++ 的 Boost.Asio。 所謂「非同步 IO」,就是你發起一個 IO 操作,卻不用等它結束,你可以繼續做其他事情,當它結束時,你會得到通知。 Asyncio 是併發(concurrency)的一種方式。對 Python 來說,
Python之路(第三十八篇) 併發程式設計:程序同步鎖/互斥鎖、訊號量、事件、佇列、生產者消費者模型
一、程序鎖(同步鎖/互斥鎖) 程序之間資料不共享,但是共享同一套檔案系統,所以訪問同一個檔案,或同一個列印終端,是沒有問題的, 而共享帶來的是競爭,競爭帶來的結果就是錯亂,如何控制,就是加鎖處理。 例子 #併發執行,效率高,但競爭同一列印終端,帶來了列印錯亂 from multiproc
【轉】Java併發程式設計:同步容器
為了方便編寫出執行緒安全的程式,Java裡面提供了一些執行緒安全類和併發工具,比如:同步容器、併發容器、阻塞佇列、Synchronizer(比如CountDownLatch)。今天我們就來討論下同步容器。 一、為什麼會出現同步容器? 在Java的集合容器框架中,主要有四大類別:Li
python程式設計:從入門到實踐 pdf 下載
上到有程式設計基礎的程式設計師,下到10歲少年,想入門Python並達到可以開發實際專案的水平,本書是讀者優選! 本書是一本全面的從入門到實踐的Python程式設計教程,帶領讀者快速掌握程式設計基礎知識、編寫出能解決實際問題的程式碼並開發複雜專案。 書中內容分為基礎篇和
python程式設計:從入門到實踐學習筆記-Django開發使用者賬戶(一)
讓使用者能夠輸入資料(表單) 在建立使用者賬戶身份驗證系統之前,先新增幾個頁面,讓使用者能偶輸入資料。新增新主題、新增新條目以及編輯既有條目。 新增新主題 1.用於新增主題的表單 建立一個forms.py檔案與models.py放在同一目錄下。 from django import
python程式設計:從入門到實踐學習筆記-Django入門(四)
建立其他網頁 我們接下來擴充“學習筆記”專案,建立兩個顯示資料的網頁,其中一個列出所有的主題,另一個顯示特定主題的所有條目。 模板繼承 編寫一個包含通用元素的父模板,並讓每個網頁都繼承這個模板,而不必在每個網頁中重複定義這些通用元素。這樣我們可以專注於開發每個網頁的獨特部分。1.父模板
python程式設計:從入門到實踐學習筆記-Django入門(二)
建立網頁:學習筆記主頁 使用django建立網頁通常分三個階段:定義URL、編寫檢視和編寫模板。 首先必須定義URL模式,其描述了URL是如何設計的,讓django知道如何將瀏覽器請求與網站URL匹配,以確定返回哪個網頁。每個URL都被對映到特定的檢視——檢視函式獲取並處理網頁所需的資料。檢視函
python程式設計:從入門到實踐學習筆記Django入門(一)
建立應用程式 django專案由一系列應用程式組成,他們協同工作,讓專案稱謂一個整體。首先我們執行命令python manage.py startapp learning_logs。 定義模型 開啟剛剛我們建立的資料夾,並修改mod
python程式設計:從入門到實踐學習筆記-類
面向物件程式設計時,都會遇到一個概念,類,python也有這個概念,下面我們通過程式碼來深入瞭解下。 建立和使用類 class Dog(): def __init__(self, name, age):  
python程式設計:從入門到實踐學習筆記-檔案和異常
從檔案中讀取資料 讀取檔案、檔案路徑 #pi_digits.txt 3.1415926535 8979323846 2643383279 #file_reader.py fillename = 'pi_digits.txt' #讀取整個檔案 with
python程式設計:從入門到實踐學習筆記-字典
字典類似於通過聯絡人名字查詢聯絡人電話號碼的電話本,即把鍵(名字)和值(電話號碼)聯絡在一起。注意,鍵必須是唯一的。並且python只能使用不可變的物件(比如字串)來作為字典的鍵,但是可以將不可變或可變的物件作為字典的值。舉一個簡單的字典例子。 alien = {'color': 'gre
Learning-Python【29】:網路程式設計之粘包
粘包問題 上一篇部落格遺留了一個問題,在接收的最大位元組數設定為 1024 時,當接收的結果大於1024,再執行下一條命令時還是會返回上一條命令未執行完成的結果。這就是粘包問題。 因為TCP協議又叫流式協議,每次傳送給客戶端的資料實際上是傳送到客戶端所在作業系統的快取上,客戶端就是一個應用程式,需
PYTHON——多執行緒:同步鎖:Lock
一、先用一個例子說明沒有用鎖的時候,達到的效果錯誤: 1、例項(沒有鎖的情況): import time import threading # 沒有用鎖的 # 時候,出現多個執行緒拿到相同資源的現象。 # 如下例中,共享資源變數num=100,我們開100個執行緒,每個執行緒將資源變數num減1,按
python程式設計:從入門到實踐學習筆記-函式
定義函式 舉個簡單的例子 def greet_user(username): """先是簡單的問候語""" print("Hello! " + username.title() + "!") greet_user("mike") 執
高併發程式設計:同步類容器的問題
同步容器類存在的問題 同步類容器都是執行緒安全的,但在某些場景下可能需要加鎖來保護複合操作,在複合操作,如:迭代、跳轉已經條件運算中,這些操作可能會表現出意外的行為,最經典的便是ConcurrentModificationException,原因是當容器迭代的過程中,被併發的修改了內
Python程式設計:pyenv管理多個python版本環境
pyenv 多版本的Python管理工具 https://github.com/pypa/pipenv pyenv-virtualenv 虛擬環境管理工具 https://github.com/pyenv/pyenv-virtualenv 安裝 mac系統下 $ brew
Python程式設計:records庫操作SQL查詢MySQL資料庫
records庫可以快速的查詢資料庫,比操作遊標cursor物件要好使,還支援匯出為具體格式 支援:RedShift, Postgres, MySQL, SQLite, Oracle, and MS-SQL 不過作者沒有寫清楚依賴包,所以遇到一點點問題,好在順利解決 專案地址