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上下文,那麼程序還是可以運行的。

不是嚴格意義上的協程
xxxxxxxxxximport timedef 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輸出結果:
xxxxxxxxxxlanghgigglelanghgiggle
xxxxxxxxxximport 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() # 發送取消信號給 task2if __name__ == "__main__": # 確保只在直接運行本文件時執行 asyncio.run(main()) # 啟動事件循環並執行 main() 協程輸出結果:
xxxxxxxxxxlanghgigglelanghgigglelanghgigglelanghgigglelanghgigglelanghgigglelanghgigglelanghgigglelanghgigglelanghgiggle程序結束!如果一個線程裡面IO操作比較多的時候,可以用協程 Input/ Output 常見的IO操作:文件操作、網絡請求
適合高併發處理
本Project有python版本,即.venv版本。應在其中安裝。
在Python右上角的搜索中,搜索Python Interpreter。

xxxxxxxxxx# 導入greenlet模塊from greenlet import greenletdef 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,不斷切換輸出結果:
xxxxxxxxxxI am eating my lunch.I am drinking my coffee.I have finished my lunch.I have finished my coffee.
👉🏻 以下是官方協程
xxxxxxxxxximport asyncioasync 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())輸出結果:
xxxxxxxxxxI am eating my lunch.I am drinking my coffee.I have finished my lunch.I have finished my coffee.⚠️注意:文件命名不要和第三方模塊或內置模塊重名

xxxxxxxxxx# 導入gevent模塊import gevent3.1 基本方法
gevent.spawn(函數名):創建協程對象
gevent.sleep():耗時操作
gevent.join():阻塞,等待某個協程執行結束
gevent.joinall():等待所有協程對象都執行結束再退出,參數是一個協程對象列表

xxxxxxxxxxdef 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()輸出結果:
xxxxxxxxxxI am eating my lunch.I am drinking my coffee.I have finished my coffee.I have finished my lunch.joinall():等待所有協程都執行結束再退出

xxxxxxxxxxdef 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"), ])輸出結果:
xxxxxxxxxxAntonio 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.
xxxxxxxxxxfrom gevent import monkeymonkey.patch_all() # 將用到的time.sleep()替換成gevent裡面自己實現耗時操作的gevent.sleep()代碼# ⚠️注意:monkey.patch_all()必須放在補丁者都前面import timedef 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"), ])輸出結果:
xxxxxxxxxxAntonio 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密集型操作(科學計算、對影片進行高清解碼、計算圓周率等)
進程、線程、協程都可以完成多任務,可根據自己開發的需要選擇使用