1.協程的概念
1.1 定義
程序是作業系統內部執行的程式
執行緒是程序內部執行的程式
協程是執行緒內部執行的程式
協程是單執行緒下的併發,又成微執行緒,英文名coroutine
1.2 協程的優點
協程切換的開銷更小
GIL鎖導致同一時刻只能執行一個執行緒,一個執行緒內不會限制協程數,單執行緒就可以實現併發的效果,最大程度利用CPU
1.3 協程的缺點
協程的本質是單執行緒下的多工處理,無法利用多核優勢
如果協程發生阻塞,在沒有使用非同步I/O的情況下,那麼整個執行緒將阻塞,所線上程的其它協程任務都不能執行
2. 協程的操作
2.1 協程函式
加上async關鍵字的函式就是協程函式
async def f1():
...
2.2 協程物件
協程函式呼叫後的返回值就是協程物件
async def f1():
print(111)
res = f1()
print(res)
按一般函式的呼叫方法呼叫協程函式不會報錯,但是會發出警告
按一般函式的呼叫方法呼叫協程函式,函式內部的程式碼不會執行,只是會返回一個協程物件。
2.3 asyncio模組
Python 3.4:asyncio 被正式引入標準庫,是一個實現非同步程式設計的模組。
Python 3.5:引入了 async 和 await 關鍵字,這使得協程的定義和使用更加直觀和簡潔。這些關鍵字取代了早期版本中的 @asyncio.coroutine 裝飾器和 yield from 語法。
2.4 事件迴圈的概念
事件迴圈可以類比while迴圈來理解,在迴圈週期內執行一些任務,特定的條件下結束迴圈。
import asyncio
loop = asyncio.get_event_loop()
2.5 協程函式的兩種呼叫方法,以直譯器3.10版本為例
由協程物件的定義可知,按函式名( )無法呼叫協程函式,需要協程物件和事件迴圈配合才能實現
[1]方法一:
import asyncio
async def work():
print(666)
return 'work函式的返回值'
def create_coroutine():
obj = work() # 1.呼叫協程函式,生成協程物件
circle = asyncio.get_event_loop() # 2.建立一個事件迴圈
circle.run_until_complete(obj) # 3.將協程物件當作任務提交到事件迴圈的任務列表中,協程執行完成事件迴圈停止
if __name__ == '__main__':
create_coroutine()
在 Python 3.10 中,這個警告是因為在沒有活動事件迴圈的主執行緒中呼叫 asyncio.get_event_loop() 時,get_event_loop() 會自動建立一個新的事件迴圈,這種方式被認為是不推薦的。
為了避免這個警告,可以使用 asyncio.run() 來執行協程,這是從 Python 3.7 開始推薦的做法。asyncio.run() 會自動建立和關閉事件迴圈,簡化了非同步程式碼的編寫。
[2]方法二:
import asyncio
async def work():
print(666)
return 'work函式的返回值'
def create_coroutine():
obj = work() # 1.呼叫協程函式,生成協程物件
asyncio.run(obj) # 2.呼叫run函式啟動協程物件
if __name__ == '__main__':
create_coroutine()
方式二的本質和方式一是一樣的,內部先建立事件迴圈,然後執行run_until_complete
需要注意的是,run函式在直譯器3.7才加入
2.6 await關鍵字
await關鍵字用於等待一個async(非同步)函式的結果,使用await可以讓程式在等待某個操作完成的同時不阻塞整個事件迴圈,從而允許其它任務執行,對於I/O密集型的應用特別有用。
await關鍵字解決了一個協程阻塞而導致整個執行緒阻塞的問題。
注意事項:
1.使用await必須在async(非同步)函式中,不能在非非同步函式中使用。
2.await只能用於非同步函式返回的協程物件。
3.在非同步函式內部,await可以用來等待另一個非同步函式的結果,這會暫停當前協程的執行,直到等待的協程完成,然後繼續執行。
程式碼示例
import asyncio
async def f1():
print('非同步函式執行開始')
await asyncio.sleep(1) # 模擬非同步操作,比如網路請求
print('非同步函式執行結束')
return 666
async def f2():
res = await f1()
print(res)
asyncio.run(f2()) # 獲取事件迴圈並執行f2()協程
分析:
f1是一個非同步函式,使用了await來等待asyncio.sleep(1)。這實際上並不會讓程式等待1秒,而是允許事件迴圈在這1秒內執行其它任務。
f2也是一個非同步函式,等待f1的執行結果。
ascyncio.run(f2())啟動事件迴圈並執行f2協程。
2.7 Task物件
[1]概念
前面的協程程式碼都只建立了一個任務,即事件迴圈的列表中只有一個任務物件;如需在程式中建立多個任務物件,需要使用Task。
Task用於併發排程協程,透過asyncio.create(協程物件)的方式建立Task物件,可以讓協程加入事件迴圈中等待被排程執行。
注意事項:
asyncio.create_task( )函式在python3.7中被加入,在之前的版本可以改用底層級的asyncio.ensure_future( )函式。
[2]建立多工方式一
逐個建立任務
import asyncio
# 定義協程功能函式
async def work():
print('協程執行開始')
await asyncio.sleep(1) # 模擬非同步操作,比如網路請求
print('協程執行結束')
return 666
# 定義產生協程函式
async def create_coroutine():
print('產生協程的函式執行開始')
task1 = asyncio.create_task(work()) # 將work()得到的協程物件封裝到Task物件中,並立即新增到事件迴圈的任務列表中,等待事件迴圈
task2 = asyncio.create_task(work()) # 將work()得到的協程物件封裝到Task物件中,並立即新增到事件迴圈的任務列表中,等待事件迴圈
response1 = await task1 # 引用了await之後,task1遇到sleep不會阻塞整個執行緒
response2 = await task2
print(response1, response2)
print('產生協程的函式執行結束')
if __name__ == '__main__':
asyncio.run(create_coroutine())
[3]建立多工方式二
使用列表生成式生成任務
import asyncio
# 定義協程功能函式
async def work(num):
print('協程執行開始')
await asyncio.sleep(1) # 模擬非同步操作,比如網路請求
print('協程執行結束')
return num * num
# 定義產生協程函式
async def create_coroutine():
print('產生協程的函式執行開始')
# 將work()得到的協程物件封裝到Task物件中,並立即新增到事件迴圈的任務列表中,等待事件迴圈
task_list = [asyncio.create_task(work(i)) for i in range(1, 4)]
# 引用了await之後,task1遇到sleep不會阻塞整個執行緒
# 如果設定了timeout值,則意味著此處最多等待的秒,完成的協程返回值寫入到done中,未完成則寫到pending中
# wait裡面要放可迭代物件
done, pending = await asyncio.wait(task_list, timeout=None)
print(done)
print(pending)
print('產生協程的函式執行結束')
if __name__ == '__main__':
asyncio.run(create_coroutine())
[3]獲取協程返回值
在步驟[2]的基礎上使用async.gather()獲取返回值
import asyncio
# 定義協程功能函式
async def work(num):
print('協程執行開始')
await asyncio.sleep(1) # 模擬非同步操作,比如網路請求
print('協程執行結束')
return num * 10
# 定義產生協程函式
async def create_coroutine():
print('產生協程的函式執行開始')
# 將work()得到的協程物件封裝到Task物件中,並立即新增到事件迴圈的任務列表中,等待事件迴圈
task_list = [asyncio.create_task(work(i)) for i in range(1, 4)]
# 引用了await之後,task1遇到sleep不會阻塞整個執行緒
# 如果設定了timeout值,則意味著此處最多等待的秒,完成的協程返回值寫入到done中,未完成則寫到pending中
# wait裡面要放可迭代物件
done, pending = await asyncio.wait(task_list, timeout=None)
response = await asyncio.gather(*task_list)
print(response)
print('產生協程的函式執行結束')
if __name__ == '__main__':
asyncio.run(create_coroutine())
需要注意的是,以上程式碼即使不執行 done, pending = await asyncio.wait(task_list, timeout=None),也能獲取到返回值。