前言
今天開始聊一聊python3的asyncio。關於asyncio,大家肯定都有自己的理解,並且網上大神也把基礎概念也解釋的比較透徹。
本文寫作的初衷,主要是理解asyncio的原理並且實現一遍。
話不多說,我們開始!
一、知識準備
● 理解程式、執行緒、協程。簡單來說,這三個都是為了解決多工同時進行的問題
1)程式是操作資源分配的最小單位,多工的實現主要是極快地在程式間來回切換,而程式切換消耗時間最長(系統呼叫)
2)執行緒依賴於程式,多個執行緒共享了父程式的一部分資源,執行緒切換時間相對於程式來說消耗時間大大減少,但是由於python gil的存在,並不存在多執行緒(系統呼叫)
3)協程依賴於執行緒,由於程式/執行緒切換都是系統呼叫,開銷是巨大的。而協程是在使用者空間內完成任務切換,不會切換到作業系統資源(暫存器、訊號量、堆疊等),所以這種方式開銷最小。python的協程核心在於,遇到等待事件,就交出cpu控制權,轉而讓其他協程執行
● 理解python生成器,yield/yield from
這裡就不班門弄斧了,直接推薦大佬的blog
● 理解關鍵字async/await,async/await是3.5之後的語法,和yield/yield from異曲同工
二、環境準備
元件 | 版本 |
---|---|
python | 3.7.7 |
三、
run_until_complete
的實現
先來看下官方asyncio的使用方法:
|># more main.py
import asyncio
async def hello():
print('enter hello ...')
return 'world'
if __name__ == "__main__":
loop = asyncio.get_event_loop()
task = loop.create_task(hello())
rst = loop.run_until_complete(task)
print(rst)
|># python3 main.py
enter hello ...
world
來看下造的輪子的使用方式:
▶ more main.py
from wilsonasyncio import get_event_loop
async def hello():
print('enter hello ...')
return 'world'
if __name__ == "__main__":
loop = get_event_loop()
task = loop.create_task(hello())
rst = loop.run_until_complete(task)
print(rst)
▶ python3 main.py
enter hello ...
world
自己造的輪子也很好的執行了,下面我們來看下輪子的程式碼
四、程式碼解析
1)程式碼組成
|># tree
.
├── eventloops.py
├── futures.py
├── main.py
├── tasks.py
├── wilsonasyncio.py
檔案 | 作用 |
---|---|
eventloops.py | 事件迴圈 |
futures.py | futures物件 |
tasks.py | tasks物件 |
wilsonasyncio.py | 可呼叫方法集合 |
main.py | 入口 |
2)程式碼概覽:
eventloops.py
類/函式 | 方法 | 物件 | 作用 | 描述 |
---|---|---|---|---|
Eventloop | 事件迴圈,一個執行緒只有執行一個 | |||
__init__ |
初始化兩個重要物件 self._ready 與 self._stopping |
|||
self._ready |
所有的待執行任務都是從這個佇列取出來,非常重要 | |||
self._stopping |
事件迴圈完成的標誌 | |||
call_soon |
呼叫該方法會立即將任務新增到待執行佇列 | |||
run_once |
被run_forever 呼叫,從self._ready 佇列裡面取出任務執行 |
|||
run_forever |
死迴圈,若self._stopping 則退出迴圈 |
|||
run_until_complete |
非常重要的函式,任務的起點和終點(後面詳細介紹) | |||
create_task |
將傳入的函式封裝成task 物件,這個操作會將task.__step 新增到__ready 佇列 |
|||
Handle |
所有的任務進入待執行佇列(Eventloop.call_soon )之前都會封裝成Handle物件 |
|||
__init__ |
初始化兩個重要物件 self._callback 與 self._args |
|||
self._callback |
待執行函式主體 | |||
self._args |
待執行函式引數 | |||
_run |
待執行函式執行 | |||
get_event_loop |
獲取當前執行緒的事件迴圈 | |||
_complete_eventloop |
將事件迴圈的_stopping 標誌置位True |
tasks.py
類/函式 | 方法 | 物件 | 作用 | 描述 |
---|---|---|---|---|
Task | 繼承自Future,主要用於整個協程執行的週期 | |||
__init__ |
初始化物件 self._coro ,並且call_soon 將self.__step 加入self._ready 佇列 |
|||
self._coro |
使用者定義的函式主體 | |||
__step |
Task類的核心函式 |
futures.py
類/函式 | 方法 | 物件 | 作用 | 描述 |
---|---|---|---|---|
Future | 主要負責與使用者函式進行互動 | |||
__init__ |
初始化兩個重要物件 self._loop 與 self._callbacks |
|||
self._loop |
事件迴圈 | |||
self._callbacks |
回撥佇列,任務暫存佇列,等待時機成熟(狀態不是PENDING ),就會進入_ready 佇列 |
|||
add_done_callback |
新增任務回撥函式,狀態_PENDING ,就虎進入_callbacks 佇列,否則進入_ready 佇列 |
|||
set_result |
獲取任務執行結果並儲存至_result ,將狀態置位_FINISH ,呼叫__schedule_callbacks |
|||
__schedule_callbacks |
將回撥函式放入_ready ,等待執行 |
|||
result |
獲取返回值 |
3)執行過程
3.1)入口函式
main.py
async def hello():
print('enter hello ...')
return 'world'
if __name__ == "__main__":
loop = get_event_loop()
task = loop.create_task(hello())
rst = loop.run_until_complete(task)
print(rst)
loop = get_event_loop()
獲取事件迴圈task = loop.create_task(hello())
將使用者函式hello()
封裝成協程,我們看下create_task的原始碼
def create_task(self, coro):
task = tasks.Task(coro, loop=self)
return task
初始化一個Task物件,從程式碼概覽得知,初始化物件之後會立即將__step
新增到_ready
佇列等待執行
rst = loop.run_until_complete(task)
開始執行事件迴圈的第一個函式run_until_complete
3.2)事件迴圈啟動
eventloops.py
def run_until_complete(self, future):
future.add_done_callback(_complete_eventloop, future)
self.run_forever()
return future.result()
future.add_done_callback(_complete_eventloop, future)
為future
新增回撥函式(future
就是task物件,而task物件裡的任務就是hello()
),回撥函式是_complete_eventloop
。就是future執行完成之後執行_complete_eventloop
self.run_forever()
啟動事件迴圈
3.3)第一次迴圈run_forever
--> run_once
eventloops.py
def run_once(self):
ntodo = len(self._ready)
for _ in range(ntodo):
handle = self._ready.popleft()
handle._run()
- 將
_ready
佇列的內容(task.__step
)取出來執行
tasks.py
def __step(self, exc=None):
coro = self._coro
try:
if exc is None:
coro.send(None)
else:
coro.throw(exc)
except StopIteration as exc:
super().set_result(exc.value)
finally:
self = None
coro.send(None)
核心程式碼,跳轉回到使用者函式hello()
main.py
async def hello():
print('enter hello ...')
return 'world'
- 使用者函式非常簡單,列印一行資料,以及返回一個字串
world
,執行完成之後回到task.__step()
super().set_result(exc.value)
由於使用者函式執行完成,會丟擲異常StopIteration
,捕獲之後執行set_result
- 由程式碼概覽得知
set_result
的作用在於將任務狀態置位_FINISHED
,並且將回撥函式(_complete_eventloop
)寫入_ready
佇列
3.4)第二次迴圈run_forever
--> run_once
eventloops.py
def run_once(self):
ntodo = len(self._ready)
for _ in range(ntodo):
handle = self._ready.popleft()
handle._run()
- 繼續迴圈,
handle
封裝了_complete_eventloop
def _complete_eventloop(fut):
fut._loop.stop()
- 呼叫stop,設定停止標誌
3.5)第三次迴圈run_forever
def run_forever(self):
while True:
self.run_once()
if self._stopping:
break
- 跳出事件迴圈,回到
run_until_complete
def run_until_complete(self, future):
future.add_done_callback(_complete_eventloop, future)
self.run_forever()
return future.result()
3.6)回到主函式,獲取返回值
if __name__ == "__main__":
loop = get_event_loop()
task = loop.create_task(hello())
rst = loop.run_until_complete(task)
print(rst)
rst = loop.run_until_complete(task)
獲取返回值
3.7)執行結果
▶ python3 main.py
enter hello ...
return world ...
五、流程總結
六、小結
● task物件與future有什麼區別?主要用於整個協程執行的週期,主要負責與使用者函式進行互動
● 本文從asyncio的第一個函式run_until_complete
,介紹了asyncio的基本流程:使用者函式並不是立即執行,而是進入佇列,然後根據eventloop
在合適的時機進行統一排程
● 本文中的程式碼,參考了python 3.7.7中asyncio的原始碼,裁剪而來
● 本文中程式碼:程式碼
至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教...