python3協程數量限制 多工 3 協程

2021-10-11 22:17:47 字數 4729 閱讀 8600

**環境:python3.6

非同步io

我們知道,cpu 速度遠遠快於磁碟、網路等 io。在 io 程式設計中,假如乙個 io 操作阻塞了當前執行緒,會導致其他**無法執行,所以我們使用多執行緒或者多程序來併發執行**。

針對這個問題,我們需要另一種解決方法:非同步 io。

非同步 io,即當**需要執行乙個耗時的 io 操作時,它只發出 io 指令,並不等待 io 結果,然後就去執行其他**了。一段時間後,當 io 返回結果時,再通知 cpu 進行處理。

python中最初的協程

了解最初的協程有助於我們理解後面現代協程的用法。

協程這個概念並不是 python 首次提出的,而是從其他語言借鑑過來的。

我們知道,兩個普通函式的呼叫是按順序的,比如a函式呼叫b函式,b執行完畢返回結果給a,a執行完畢。

協程看上去也是函式,如果協程a呼叫協程b,在執行過程中,協程b可以中斷,轉而執行a,再在適當的時候返回b接著從中斷處往下執行。

協程的這種執行特點,恰好符合我們的需求:通過協程實現非同步 io 程式設計。

生成器進化成協程

python 基於 generator 進行一系列功能改進後得到協程,語法上都是定義體中包含 yield 關鍵字。

在協程中,yield 不僅可以返回值,還能接收呼叫者通過.send()方法發出的引數。yield 通常出現在表示式右邊,如:data = yield something。如果 yield 後面沒有表示式,說明此時 yield 只負責接收資料,協程始終返回none。

簡單協程的基本行為

舉個簡單例子:

in [1]: def my_coroutine():

...: print('協程被啟用')

...: while true:

# yield 後面不跟表示式,這裡只接收 send() 傳過來的資料

...: x = yield

...: print(f'協程接收到引數:')

in [2]: my_corou = my_coroutine()

# 可檢視協程當前狀態

in [3]: from inspect import getgeneratorstate

in [4]: getgeneratorstate(my_corou)

out[4]: 'gen_created'

# 啟用協程,此處可用 my_corou.send(none) 代替

in [5]: next(my_corou)

協程被啟用

in [6]: getgeneratorstate(my_corou)

out[6]: 'gen_suspended'

in [7]: return_value = my_corou.send(99)

協程接收到引數:99

in [8]: print(return_value)

none

in [9]: my_corou.close()

in [10]: getgeneratorstate(my_corou)

out[10]: 'gen_closed'

通過例子我們主要了解的是,協程需要手動啟用才能真正呼叫,協程在不需要的時候要記得關閉。

用協程改進生產者-消費者模型

傳統生產者-消費者模型中,乙個執行緒寫訊息,另乙個執行緒取訊息,通過鎖機制控制佇列和等待,但是一不小心可能死鎖。

如果改用協程,生產者生產訊息後,直接通過 yield 跳轉到消費者開始執行,待消費者執行完畢後,再切換回生產者繼續生產,整個流程無鎖且效率高:

from inspect import getgeneratorstate

def consumer():

r = '200 ok'

while true:

# yield接收生產者的資料賦值給n,並把處理結果狀態r返回

n = yield r

print(f'[consumer] 消費了:')

def producer(c):

# 別忘了啟用協程

c.send(none)

n = 0

while n < 5:

n = n + 1

print(f'[producer] 生產了:')

# 一旦生產了東西,通過c.send()切換到consumer執行

# consumer處理資料後通過yield返回結果狀態,這裡獲取返回內容

r = c.send(n)

print(f'[producer] 消費者返回的處理結果:')

print(f'生產者不生產了,看看當前consumer狀態:')

c.close()

print(f'關閉consumer,看看當前consumer狀態:')

if __name__ == "__main__":

producer(consumer())

上面例子整個流程只由乙個執行緒執行且無鎖,生產者和消費者協作完成任務,這種屬於協作式多工,跟多執行緒這種搶占式多工要區分開。

asyncio

在 python3.4 版本中,開始引入標準庫asyncio直接內建了對非同步 io 的支援。

asyncio的程式設計模型就是乙個訊息迴圈。我們從asyncio模組中直接獲取乙個eventloop的引用,然後把需要執行的協程扔到eventloop中執行,就實現了非同步 io。

先簡單介紹下asyncio涉及到的一些詞語:

future:乙個物件,表示非同步執行的操作。通常情況下自己不應該建立future,而只能由併發框架如asyncio例項化。

task:在eventloop中負責執行協程的任務,是future的子類。換句話說,task就是future,但反過來不一定。

下面是asyncio常用api:

asyncio.get_event_loop():獲取乙個eventloop物件,用來執行協程

asyncio.iscoroutine(obj):判斷乙個物件是否是協程。

asyncio.sleep(delay):直接當做是乙個耗時多少秒的協程即可。

asyncio.ensure_future(coro_or_future):入參是協程,則啟用協程,返回乙個task物件;如果入參是future,則將入參直接返回。

asyncio.gather(coros_or_futures):按入參中協程的順序儲存協程的執行結果,大部分情況下使用。

asyncio.wait(futures):對比gather,不一定按入參順序返回執行結果。返回包含已完成和掛起的task,可通過接收引數return_when選擇返回結果的時機,按實際情況使用。

我們將在下面結合新的關鍵字async/await來舉例說明。

async/await

為了簡化使用和標識非同步 io,從 python3.5 版本開始引入新的語法糖async/await,用async把乙個generator標記為協程函式,然後在協程內部用await呼叫另乙個協程實現非同步操作。

注意:用async標記協程函式,呼叫該函式時協程尚未啟用,啟用該函式可以用await或者yield from,也可以通過ensure_future()或者abstracteventloop.create_task()排程執行。

舉個例子:

from asyncio import sleep as aiosleep, gather, get_event_loop

async def compute(x, y):

print("計算 %s + %s ..." % (x, y))

await aiosleep(1)

return x + y

async def print_sum(x, y):

result = await compute(x, y)

print("%s + %s = %s" % (x, y, result))

async def coro_main():

'''一般我們會寫乙個 coroutine 的 main 函式,專門負責管理協程'''

await gather(print_sum(1, 2), print_sum(4, 9))

def main():

aioloop = get_event_loop()

# 內部使用ensure_future()啟用協程

aioloop.run_until_complete(coro_main())

aioloop.close()

if __name__ == "__main__":

main()

執行結果:

計算 1 + 2 ...

計算 4 + 9 ...

(暫停約1秒,實際輸出沒有這行)

1 + 2 = 3

4 + 9 = 13

觀察例子執行結果,我們看到:

當協程開始計算1+2前還有乙個耗時 1 秒的 io 操作,當前執行緒並未等待,而是去執行其他協程計算4+9,實現了併發執行。

協程結果按gather入參的順序列印。

總結面對 cpu 高速執行和 io 裝置的龜速嚴重不匹配問題,我們至少要知道兩種解決方法:使用多程序和多執行緒併發執行**;使用非同步 io 執行**。

python 協程是基於生成器改進後得到的,底部實現都是定義體中包含yield關鍵字。

協程屬於協作式多工,整個流程無需鎖,跟多執行緒這種搶占式多工要區分開。

asyncio支援非同步 io,我們從asyncio模組中直接獲取乙個eventloop的引用,然後把需要執行的協程扔到eventloop中執行,就實現了非同步 io。

定義協程函式時,我們用async標記協程函式,然後在協程內部用await呼叫另乙個協程實現非同步操作。

python 3 協程函式

1 把函式的執行結果封裝好 iter 和 next 即得到乙個迭代器 2 與return功能類似,都可以返回值,但不同的是,return只能返回一次值,而yield可以返回多次值 3 函式暫停與再繼續的狀態是由yield儲存的 def func count print start while tru...

python3協程學習筆記

在此之前,協程對我來說是乙個比較陌生的概念,學習之後,發現其應用場景還是有不少,大師之言 協程能自然地表述很多演算法,例如 遊戲 非同步i o,以及其他事件驅動型邊吃形式活協作式多工。舉例來說,asyncio tornado twisted simpy庫都是基於協程特性在單個執行緒中管理多個併發活動...

Python3學習筆記 清晰理解協程

在了解協程之前,我們先簡單了解一下程序與執行緒,併發與並行的概念。什麼是協程?我們先來看下對協程的概括 協程被稱為微執行緒或者纖程,是一種使用者態的輕量級執行緒。其本質就是乙個單執行緒,協程的作用就是在乙個執行緒中人為控制 塊的執行順序。記住這句就可以了 具體解釋如下 在乙個執行緒中有很多函式,我們...