網站首頁 編程語言 正文
Python異步編程之Asyncio
1. 協程簡介
1.1 協程的含義及實現方法
協程(Coroutine),也可以被稱為微線程,是一種用戶態內的上下文切換技術。簡而言之,其實就是通過一個線程實現代碼塊相互切換執行。例如:
def func1(): print(1) ... # 協程介入 print(2) def func2(): print(3) ... # 協程介入 print(4) func1() func2()
上述代碼是普通的函數定義和執行,按流程分別執行兩個函數中的代碼,并先后會輸出:1、2、3、4
。但如果介入協程技術那么就可以實現函數見代碼切換執行,最終輸入:1、3、2、4
。
在Python中有多種方式可以實現協程,例如:
- greenlet,是一個第三方模塊,用于實現協程代碼(Gevent協程就是基于greenlet實現);
- yield,生成器,借助生成器的特點也可以實現協程代碼;
- asyncio,在Python3.4中引入的模塊用于編寫協程代碼;
- async & awiat,在Python3.5中引入的兩個關鍵字,結合asyncio模塊可以更方便的編寫協程代碼。
前兩種實現方式較為老舊,所以重點關注后面的方式
標準庫實現方法
asyncio是Python 3.4版本引入的標準庫,直接內置了對異步IO的支持。
import asyncio @asyncio.coroutine def func1(): print(1) yield from asyncio.sleep(2) # 遇到IO耗時操作,自動化切換到tasks中的其他任務 print(2) @asyncio.coroutine def func2(): print(3) yield from asyncio.sleep(2) # 遇到IO耗時操作,自動化切換到tasks中的其他任務 print(4) tasks = [ asyncio.ensure_future( func1() ), asyncio.ensure_future( func2() ) ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
關鍵字實現方法
async & await
關鍵字在Python3.5版本中正式引入,代替了asyncio.coroutine
裝飾器,基于他編寫的協程代碼其實就是上一示例的加強版,讓代碼可以更加簡便可讀。
import asyncio async def func1(): print(1) await asyncio.sleep(2) # 耗時操作 print(2) async def func2(): print(3) await asyncio.sleep(2) # 耗時操作 print(4) tasks = [ asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
1.2 案例演示
例如:用代碼實現下載 url_list
中的圖片。
方式一:同步編程實現
# requests庫僅支持同步的http網絡請求 import requests def download_image(url): print("開始下載:",url) # 發送網絡請求,下載圖片 response = requests.get(url) # 圖片保存到本地文件 file_name = url.rsplit('_')[-1] with open(file_name, mode='wb') as file_object: file_object.write(response.content) print("下載完成") if __name__ == '__main__': url_list = [ 'https://www.1.jpg', 'https://www.2.jpg', 'https://www.3.jpg' ] for item in url_list: download_image(item)
輸出:按順序發送請求,請求一次下載一張圖片,假如每次下載花費1s,完成任務需要3s 以上。
方式二:基于協程的程實現
# aiohttp 為支持異步編程的http請求庫 import aiohttp import asyncio async def fetch(session, url): print("發送請求:", url) async with session.get(url, verify_ssl=False) as response: content = await response.content.read() file_name = url.rsplit('_')[-1] with open(file_name, mode='wb') as file_object: file_object.write(content) async def main(): async with aiohttp.ClientSession() as session: url_list = [ 'https://www.1.jpg', 'https://www.2.jpg', 'https://www.3.jpg' ] tasks = [asyncio.create_task(fetch(session, url)) for url in url_list] await asyncio.wait(tasks) if __name__ == '__main__': asyncio.run(main())
輸出:一次發送三個下載請求,同時下載,假如每次下載花費1s,完成任務僅需要1s 左右,第一種方法的耗時為第二種的三倍。
1.3 小結
協程可以讓原來要使用異步+回調方式寫的非人類代碼,用看似同步的方式寫出來。
2. 異步編程簡介
2.1 同步和異步的區別
同步 :循序漸進執行操作、請求 異步:無需等待上一步操作、請求完成,就開始下一步(每個操作仍然有先后順序)
目前python異步相關的主流技術是通過包含關鍵字async&await的async模塊實現。
2.2 異步編程-事件循環
事件循環,可以把他當做是一個while循環,這個while循環在周期性的運行并執行一些任務,在特定條件下終止循環。
# 偽代碼 任務列表 = [ 任務1, 任務2, 任務3,... ] while True: 可執行的任務列表,已完成的任務列表 = 去任務列表中檢查所有的任務,將'可執行'和'已完成'的任務返回 for 就緒任務 in 已準備就緒的任務列表: 執行已就緒的任務 for 已完成的任務 in 已完成的任務列表: 在任務列表中移除 已完成的任 如果 任務列表 中的任務都已完成,則終止循環
在編寫程序時候可以通過如下代碼來獲取和創建事件循環。
# 方式一: import asyncio # 生成或獲取一個事件循環 loop = asyncio.get_event_loop() # 將任務添加到事件循環中 loop.run_until_complete(任務) # 方式二(python3.7及以上版本支持): asyncio.run( 任務 )
2.3 異步編程-快速上手
async 關鍵字
- 協程函數:定義函數時候由async關鍵字裝飾的函數
async def 函數名
- 協程對象:執行協程函數得到的協程對象。
# 協程函數 async def func(): pass # 協程對象 result = func()
注意:執行協程函數只會創建協程對象,函數內部代碼不會執行。如果想要運行協程函數內部代碼,必須要將協程對象交給事件循環來處理。
import asyncio async def func(): print("執行協程函數內部代碼!") result = func() # 調用方法1: # loop = asyncio.get_event_loop() # loop.run_until_complete( result ) # 調用方法2: asyncio.run( result )
await 關鍵字
await + 可等待的對象(協程對象、Future、Task對象 -> IO等待),遇到IO操作掛起當前協程(任務),等IO操作完成之后再繼續往下執行。當前協程掛起時,事件循環可以去執行其他協程(任務)。
import asyncio async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值' async def func(): print("執行協程函數內部代碼") # await等待對象的值得到結果之后再繼續向下走 response = await others() print("IO請求結束,結果為:", response) asyncio.run( func() )
Task 對象
Task對象的作用是在事件循環中添加多個任務,用于并發調度協程,通過asyncio.create_task(協程對象)
的方式創建Task對象,這樣可以讓協程加入事件循環中等待被調度執行。
async def module_a(): print("start module_a") await asyncio.sleep(2) # 模擬 module_a 的io操作 print('end module_a') return 'module_a 完成' async def module_b(): print("start module_b") await asyncio.sleep(1) # 模擬 module_a 的io操作 print('end module_b') return 'module_b 完成' task_list = [ module_a(), module_b(), ] done,pending = asyncio.run( asyncio.wait(task_list) ) print(done)
2.4 案例演示
例如:用代碼實現連接并查詢數據庫的同時,下載一個APK文件到本地。
import asyncio import aiomysql import os import aiofiles as aiofiles from aiohttp import ClientSession async def get_app(): url = "http://www.123.apk" async with ClientSession() as session: # 網絡IO請求,獲取響應 async with session.get(url)as res: if res.status == 200: print("下載成功", res) # 磁盤IO請求,讀取響應數據 apk = await res.content.read() async with aiofiles.open("demo2.apk", "wb") as f: # 磁盤IO請求,數據寫入本地磁盤 await f.write(apk) else: print("下載失敗") async def excute_sql(sql): # 網絡IO操作:連接MySQL conn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='123', db='mysql', ) # 網絡IO操作:創建CURSOR cur = await conn.cursor() # 網絡IO操作:執行SQL await cur.execute(sql) # 網絡IO操作:獲取SQL結果 result = await cur.fetchall() print(result) # 網絡IO操作:關閉鏈接 await cur.close() conn.close() task_list = [get_app(), execute_sql(sql="SELECT Host,User FROM user")] asyncio.run(asyncio.wait(task_list))
代碼邏輯分析:
【step1】asyncio.run()
創建了事件循環。wait()
方法將task任務列表加入到當前的事件循環中;(注意:必須先創建事件循環,后加入任務列表,否則會報錯)
【step2】事件循環監聽事件狀態,開始執行代碼,先執行列表中的get_app()
方法,當代碼執行到async with session.get(url)as res:
時,遇到await關鍵字表示有IO耗時操作,線程會將該任務掛起在后臺執行,并切換到另外一個異步函數excute_sql()
;
【step3】當代碼執行到excute_sql()
的第一個IO耗時操作后,線程會重復先前的操作,將該任務掛起,去執行其他可執行代碼。假如此時事件循環監聽到get_app()
中的第一IO耗時操作已經執行完成,那么線程會切換到該方法第一個IO操作后的代碼,并按順序執行直到遇上下一個await裝飾的IO操作;假如事件循環監聽到excute_sql()
中的第一個IO操作先于get_app()
的第一個IO操作完成,那么線程會繼續執行excute_sql
的后續代碼;
【step4】線程會重復進行上述第3點中的步驟,直到代碼全部執行完成,事件循環也會隨之停止。
2.5 小結
一般來說CPU的耗時運算方式有:
計算密集型的操作:計算密集型任務的特點是要進行大量的計算、邏輯判斷,消耗CPU資源,比如計算圓周率、對視頻進行高清解碼等等。
IO密集型的操作:涉及到網絡、磁盤IO的任務都是IO密集型任務,這類任務的特點是CPU消耗很少,任務的大部分時間都在等待IO操作完成(因為IO的速度遠遠低于CPU和內存的速度)。
異步編程基于協程實現,如果利用協程實現計算密集型操作,因為線程在上下文之間的來回切換總會經歷類似于”計算“-->”保存“-->”創建新環境“ 的一系列操作,導致系統的整體性能反而會下降。所以異步編程并不適用于計算密集型的程序。然而在IO密集型操作匯總,協程在IO等待時間就去切換執行其他任務,當IO操作結束后再自動回調,那么就會大大節省資源并提供性能。
好了,以上內容就給大家介紹到這里,下面再次補充介紹Python關鍵字 asynico知識。
Python關鍵字 asynico
同步和異步
同步和異步是指程序的執行方式。在同步執行中,程序會按順序一個接一個地執行任務,直到當前任務完成。而在異步執行中,程序會在等待當前任務完成的同時,執行其他任務。
同步執行意味著程序會阻塞,等待任務完成,而異步執行則意味著程序不會阻塞,可以同時執行多個任務。
同步和異步的選擇取決于你的程序需求。如果你的程序需要等待某些任務完成后才能繼續,那么同步的方式可能是更好的選擇。如果你的程序可以在等待任務完成的同時繼續執行其他任務,那么異步的方式可能是更好的選擇。
asyncio
asyncio是Python的異步編程庫,用于編寫并發程序。它提供了一組基于協程的工具,可以幫助你實現異步網絡通信、并發計算等任務。
舉個例子,假設你編寫了一個程序,要向多個遠程服務器發送請求,然后等待這些服務器的響應。如果你使用同步的方式編寫程序,你可能會這樣寫:
import requests def send_request(url): response = requests.get(url) return response.text # 向服務器1發送請求 response1 = send_request("http://server1.com") # 向服務器2發送請求 response2 = send_request("http://server2.com") # 向服務器3發送請求 response3 = send_request("http://server3.com")
在這段代碼中,你會發現,當你向服務器1發送請求時,程序會等待服務器1的響應,然后再向服務器2發送請求,最后再向服務器3發送請求。這意味著,當你發送請求時,程序都會被阻塞,直到收到響應。
如果你使用asyncio來編寫程序,你可能會這樣寫:
import asyncio import aiohttp async def send_request(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() # 向服務器1發送請求 response1 = asyncio.run(send_request("http://server1.com")) # 向服務器2發送請求 response2 = asyncio.run(send_request("http://server2.com")) # 向服務器3發送請求 response3 = asyncio.run(send_request("http://server3.com"))
在這段代碼中,你會發現,當你向服務器1發送請求時,程序不會立刻等待服務器1的響應。相反,程序會立刻向服務器2和服務器3發送請求,然后等待所有響應的到來。這意味著,當你發送請求時,程序不會阻塞,而是會繼續執行其他任務。
這就是asyncio的基本用法,它可以幫助你編寫高效的異步程序。
Github主頁:https://github.com/Viceversa0 發布一些感覺有用的代碼。
原文鏈接:https://www.cnblogs.com/zh-jp/archive/2023/01/03/17023566.html
相關推薦
- 2023-01-07 基于Go語言實現選擇排序算法及優化_Golang
- 2022-09-17 詳解React?如何防止?XSS?攻擊論$$typeof?的作用_React
- 2022-09-04 go語言中的面向對象_Golang
- 2022-07-11 MongoDB分片方式及片鍵選擇
- 2022-10-18 Qt網絡編程之TCP通信及常見問題_C 語言
- 2022-05-25 ASP.NET?Core?6.0對熱重載的支持實例詳解_實用技巧
- 2022-09-13 Python判斷和循環語句的分析與應用_python
- 2022-08-30 啟動Activity但是不顯示界面
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支