先行內容
同步和異步
同步和異步關注的發送方和接收方是否協調步調一致。
同步:發送方發出請求後,等接收方發回響應以後才發下一個請求。
異步:發送方發出請求後,不等接收方發回響應,接着發送下個請求。
同步是指一個線程要等待上一個線程執行完之後纔開始執行當前的線程。
異步是指一個線程開始執行,它的下一個線程不必等待它執行完成就可以開始執行。
阻塞和非阻塞
阻塞和非阻塞關注的是程序在等待調用結果時的狀態。
當一個進程或線程被執行,他的下一個進程或線程需要等待它執行完成才能被執行,此時他的下一個進程或線程處於阻塞態。
阻塞和同步是描述一致的,只是關注點不同,同步是關注發送方和接收方的傳遞,阻塞是關注發送方和接收方傳遞的過程
我們去火車站人工購票時需要排序購票,當我們前面有其他人在進行排隊或購票時我們要等待,此時可以說我們是處於阻塞態,這種購票方式可以認爲是同步。
如果是網絡購票,我們無需考慮前面是否有人,直接在購票即可,這是一種異步行爲,這個過程是非阻塞狀態。
並行和併發
並行:兩個或者多個事件在同一時刻發生。
併發:兩個或多個事件在同一時間間隔內發生。
通俗的說並行是指兩個或多個任務同時執行,多線程是並行的;併發是兩個或多個任務進行交替執行,某一時間段內只有一個任務在執行,協程是併發的。
協作式多任務和搶佔式多任務
協程是協作式多任務的,而線程典型是搶佔式多任務的
線程
線程(thread)是操作系統能夠進行運算調度的最小單位。大部分情況下,它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。
👉Python 標準庫之 threading (線程並行)
正文
協程
協程(coroutine)是計算機程序的一類組件,推廣了協作式多任務的子程序,允許執行被掛起與被恢復。
協程通過 async/await
語法進行聲明
聲明一個協程函數:
async def f1():
pass
運行協程:
直接調用不會執行協程,標準庫 asyncio 提供了相關功能
asyncio 有三種方式執行協程
-
通過
asyncio.run()
運行一個作爲入口的協程函數import asyncio async def f1(): print("hello") asyncio.run(f1())
-
在線程函數種通過
await
運行一個協程函數import asyncio async def f2(): print("f2") async def f1(): print("f1") await f2() asyncio.run(f1())
運行結果:
f1 f2
-
通過
asyncio.create_task()
創建協程任務後使用await
執行import asyncio async def f2(): print("f2") async def f1(): print("f1") task = asyncio.create_task(f2()) await task # f1() asyncio.run(f1())
運行結果:
f1 f2
可等待對象
如果一個對象可以在 await
語句中使用,那麼它就是可等待對象。
可等待 對象有三種主要類型: 協程, 任務(Task) 和 Future。
任務就是使用 asyncio.create_task()
創建的對象;
Future是一種特殊的低層級可等待對象,一個Future代表一個異步運算的最終結果。參考傳送門
import asyncio
async def f2(a):
a.set_result("f2")
async def f1():
loop = asyncio.get_running_loop()
print(type(loop))
fut = loop.create_future()
print(type(fut))
loop.create_task(f2(fut))
print(await fut)
asyncio.run(f1())
輸出結果:
<class 'asyncio.windows_events._WindowsSelectorEventLoop'>
<class '_asyncio.Future'>
f2
一個協程併發執行的例子
import asyncio
async def f2(a):
for i in range(3):
print(a+":", i)
await asyncio.sleep(1)
async def f1():
tasks = asyncio.gather(f2("A"), f2("B"))
await tasks
print("done")
asyncio.run(f1())
輸出結果:
A: 0
B: 0
A: 1
B: 1
A: 2
B: 2
done
協程的邏輯
- A 函數執行時被中斷,傳遞一些數據給 B 函數;
- B 函數接收到這些數據後開始執行,執行一段時間後,返回一些數據到 A 函數;
- 交替執行,知道任務完成;
通過生成器理解協程:
def f1():
for i in range(3):
from_f2 = yield i
print("來自f2的值:",from_f2, i)
def f2(a):
from_f1 = a.send(None)
print('來自f1的值:', from_f1)
list = ['A', 'B', 'C']
try:
for i in list:
from_a = a.send(i)
print('來自f1的值:', from_a)
except StopIteration:
print('done')
f = f1()
f2(f)
輸出結果:
來自f1的值: 0
來自f2的值: A 0
來自f1的值: 1
來自f2的值: B 1
來自f1的值: 2
來自f2的值: C 2
done
擴展內容
生成器
引用自-維基百科
生成器,也叫作“半協程”,是協程的子集。儘管二者都可以yield多次,掛起(suspend)自身的執行,並允許在多個入口點重新進入,但它們特別差異在於,協程有能力控制在它讓位之後哪個協程立即接續它來執行,而生成器不能,它只能把控制權轉交給調用生成器的調用者[9]。在生成器中的yield語句不指定要跳轉到的協程,而是向父例程傳遞返回值。儘管如此,仍可以在生成器設施之上實現協程,這需要通過頂層的派遣器(dispatcher)例程(實質上是trampoline)的援助,它顯式的把控制權傳遞給由生成器傳回的令牌(token)所標識出的子生成器。
在不同作者和語言之間,術語“生成器”和“迭代器”的用法有着微妙的差異。有人說所有生成器都是迭代器,生成器看起來像函數而表現得像迭代器。在Python中,生成器是迭代器構造子:它是返回迭代器的函數。
參考
asyncio 是用來編寫 併發 代碼的庫,使用 async/await 語法。
asyncio.``run(coro, *, debug=False)
: 運行協程
asyncio.create_task(coro, *, name=None)
: 創建任務
coroutine asyncio.sleep(delay, result=None, *, loop=None)
: 休眠
asyncio.gather(*aws, loop=None, return_exceptions=False)
: 併發運行任務
asyncio.shield(aw, *, loop=None)
: 保護一個可等待對象防止其被取消
asyncio.wait_for(aw, timeout, *, loop=None)
: 可等待對象完成後延時一段時間
asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
: 按條件阻塞
asyncio.as_completed(aws, *, loop=None, timeout=None)
: 併發地運行 aws 集合中的 可等待對象
asyncio.run_coroutine_threadsafe(coro, loop)
: 向指定事件循環提交一個協程
asyncio.current_task(loop=None)
:返回當前運行的任務
asyncio.all_tasks(loop=None)
: 返回事件循環所運行的未完成的任務集合
asyncio.Task(coro, *, loop=None, name=None)
:
@asyncio.coroutine
: 用來標記基於生成器的協程的裝飾器
asyncio.iscoroutine(obj)
: 如果 obj 是一個 協程對象則返回 True
asyncio.iscoroutinefunction(func)
: 如果 func 是一個協程函數則返回 True。