21 - 協程📄目錄1 協程(Coroutine)介紹1.1 簡單實現協程1.1.1 真正的協程樣式1.2 應用場景2 greenlet:一個由C語言實現的協程模塊2.1 安裝greenlet2.2 ⚠️注意:greenlet屬於手動切換,當遇到IO操作,程序會阻塞,而不能進行自動切換2.3 通過greenlet實現任務的切換3 gevent:遇到IO操作時,會進行自動切換,屬於主動式切換3.2 gevent自帶耗時操作3.3 joinall()3.4 monkey補丁:擁有在模塊運行時替換的功能4 總結(線程、進程、協程)導航連結:
介紹:單線程下的開發,又稱微線程、纖程。
⚠️注意:線程和進程的操作由程序觸發系統接口,最後的執行者是系統;協程的操作則是程序員
另一種實現多任務的方式,但比線程更小、佔用更少執行單元(理解為需要的資源)。協程自帶CPU上下文。這樣只要在合適的時機,我們可以把一個協程切換到另一個協程。只要這個過程中保存或恢復CPU上下文,那麼程序還是可以運行的。
不是嚴格意義上的協程
xxxxxxxxxx
import time
def task1():
while True:
yield "langh"
time.sleep(0.3)
def task2():
while True:
yield "giggle"
time.sleep(0.3)
if __name__ == "__main__":
t1 = task1()
# print(t1) # 生成器地址
t2 = task2()
print(next(t1))
print(next(t2))
print(next(t1))
print(next(t2))
# 通過調整以上幾行的順序,實現代碼切換執行。也可以通過以下的代碼實現類似協程的輸出,但並不是協程
# a = 0
# while True:
# print(next(t1))
# print(next(t2))
# a += 1
# if a >= 10:
# print("程序結束!")
# break
輸出結果:
xxxxxxxxxx
langh
giggle
langh
giggle
xxxxxxxxxx
import asyncio # 導入 Python 標準庫 asyncio,用來處理異步 IO(協程)
async def task1(): # 定義一個協程函數(用 async def)
while True: # 無限循環
print("langh") # 輸出字符串
await asyncio.sleep(0.3) # 非阻塞延時 0.3 秒(期間事件循環可去執行其他任務)
async def task2(): # 另一個協程函數
while True:
print("giggle") # 輸出字符串
await asyncio.sleep(0.3) # 非阻塞延時 0.3 秒
async def main(): # 主協程函數
# 同時運行 task1 和 task2
t1 = asyncio.create_task(task1()) # 創建任務 t1,開始異步執行 task1()
t2 = asyncio.create_task(task2()) # 創建任務 t2,開始異步執行 task2()
# 限制運行 10 次(約 5 秒)
for _ in range(10): # 循環 10 次
await asyncio.sleep(0.3) # 每次等待 0.3 秒(讓 t1 和 t2 有機會交替運行)
print("程序結束!") # 到達循環次數後打印結束提示
# 取消協程(否則會一直跑下去)
t1.cancel() # 發送取消信號給 task1
t2.cancel() # 發送取消信號給 task2
if __name__ == "__main__": # 確保只在直接運行本文件時執行
asyncio.run(main()) # 啟動事件循環並執行 main() 協程
輸出結果:
xxxxxxxxxx
langh
giggle
langh
giggle
langh
giggle
langh
giggle
langh
giggle
langh
giggle
langh
giggle
langh
giggle
langh
giggle
langh
giggle
程序結束!
如果一個線程裡面IO操作比較多的時候,可以用協程 Input/ Output 常見的IO操作:文件操作、網絡請求
適合高併發處理
本Project有python版本,即.venv版本。應在其中安裝。
在Python右上角的搜索中,搜索Python Interpreter。
xxxxxxxxxx
# 導入greenlet模塊
from greenlet import greenlet
def eat():
print("I am eating my lunch.")
g2.switch()
print("I have finished my lunch.")
g2.switch()
def drink():
print("I am drinking my coffee.")
g1.switch()
print("I have finished my coffee.")
if __name__ == '__main__':
# 創建協程對象 greenlet(任務名),函數不要加小括號
g1 = greenlet(eat)
g2 = greenlet(drink)
g1.switch() # 切換到g1中,並運行
# g2.switch() # 沒有g2.switch,就不會運行g2,但可以在def函數時放switch,不斷切換
輸出結果:
xxxxxxxxxx
I am eating my lunch.
I am drinking my coffee.
I have finished my lunch.
I have finished my coffee.
👉🏻 以下是官方協程
xxxxxxxxxx
import asyncio
async def eat():
print("I am eating my lunch.")
await asyncio.sleep(0) # 暫停,切給其他協程
print("I have finished my lunch.")
await asyncio.sleep(0) # 再切一次
async def drink():
print("I am drinking my coffee.")
await asyncio.sleep(0) # 暫停,切給其他協程
print("I have finished my coffee.")
async def main():
# 同時啟動兩個協程,交替運行
await asyncio.gather(eat(), drink())
if __name__ == '__main__':
asyncio.run(main())
輸出結果:
xxxxxxxxxx
I am eating my lunch.
I am drinking my coffee.
I have finished my lunch.
I have finished my coffee.
⚠️注意:文件命名不要和第三方模塊或內置模塊重名
xxxxxxxxxx
# 導入gevent模塊
import gevent
3.1 基本方法
gevent.spawn(函數名):創建協程對象
gevent.sleep():耗時操作
gevent.join():阻塞,等待某個協程執行結束
gevent.joinall():等待所有協程對象都執行結束再退出,參數是一個協程對象列表
xxxxxxxxxx
def eat():
print("I am eating my lunch.")
gevent.sleep(3)
print("I have finished my lunch.")
def drink():
print("I am drinking my coffee.")
gevent.sleep(2)
print("I have finished my coffee.")
if __name__ == '__main__':
# 1. 創建協程對象
g1 = gevent.spawn(eat)
g2 = gevent.spawn(drink)
# 2. 阻塞,等待協程執行結束
g1.join()
g2.join()
輸出結果:
xxxxxxxxxx
I am eating my lunch.
I am drinking my coffee.
I have finished my coffee.
I have finished my lunch.
joinall():等待所有協程都執行結束再退出
xxxxxxxxxx
def travel(name):
for i in range(3):
gevent.sleep(1)
print(f"{name} is traveling {i} countries.")
if __name__ == '__main__':
gevent.joinall([
gevent.spawn(travel, "Antonio"),
gevent.spawn(travel, "Ben"),
])
輸出結果:
xxxxxxxxxx
Antonio is traveling 0 countries.
Ben is traveling 0 countries.
Antonio is traveling 1 countries.
Ben is traveling 1 countries.
Antonio is traveling 2 countries.
Ben is traveling 2 countries.
xxxxxxxxxx
from gevent import monkey
monkey.patch_all() # 將用到的time.sleep()替換成gevent裡面自己實現耗時操作的gevent.sleep()代碼
# ⚠️注意:monkey.patch_all()必須放在補丁者都前面
import time
def travel(name):
for i in range(3):
time.sleep(1)
print(f"{name} is traveling {i} countries.")
if __name__ == '__main__':
gevent.joinall([
gevent.spawn(travel, "Antonio"),
gevent.spawn(travel, "Ben"),
])
輸出結果:
xxxxxxxxxx
Antonio is traveling 0 countries.
Ben is traveling 0 countries.
Antonio is traveling 1 countries.
Ben is traveling 1 countries.
Antonio is traveling 2 countries.
Ben is traveling 2 countries.
線程是CPU調度的基本單位,進程是資源分配的基本單位
進程、線程、協程對比:
進程:切換需要的資源最大,效率最低
線程:切換需要的資源一般,效率一般
協程:切換需要的資源最少,效率高
多線程適合IO密集型操作(如文件讀寫、爬蟲),多進程適合CPU密集型操作(科學計算、對影片進行高清解碼、計算圓周率等)
進程、線程、協程都可以完成多任務,可根據自己開發的需要選擇使用