詳解python之異步編程
一、異步編程概述
異步編程是一種并發(fā)編程的模式,其關(guān)注點是通過調(diào)度不同任務之間的執(zhí)行和等待時間,通過減少處理器的閑置時間來達到減少整個程序的執(zhí)行時間;異步編程跟同步編程模型最大的不同就是其任務的切換,當遇到一個需要等待長時間執(zhí)行的任務的時候,我們可以切換到其他的任務執(zhí)行;
與多線程和多進程編程模型相比,異步編程只是在同一個線程之內(nèi)的的任務調(diào)度,無法充分利用多核CPU的優(yōu)勢,所以特別適合IO阻塞性任務;
python 版本 3.9.5
二、python的異步框架模型
python提供了asyncio模塊來支持異步編程,其中涉及到coroutines
、event loops
、futures
三個重要概念;
event loops主要負責跟蹤和調(diào)度所有異步任務,編排具體的某個時間點執(zhí)行的任務;
coroutines是對具體執(zhí)行任務的封裝,是一個可以在執(zhí)行中暫停并切換到event loops執(zhí)行流程的特殊類型的函數(shù);其一般還需要創(chuàng)建task才能被event loops調(diào)度;
futures負責承載coroutines的執(zhí)行結(jié)果,其隨著任務在event loops中的初始化而創(chuàng)建,并隨著任務的執(zhí)行來記錄任務的執(zhí)行狀態(tài);
異步編程框架的整個執(zhí)行過程涉及三者的緊密協(xié)作;
首先事件循環(huán)啟動之后,會從任務隊列獲取第一個要執(zhí)行的coroutine,并隨之創(chuàng)建對應task和future;
然后隨著task的執(zhí)行,當遇到coroutine內(nèi)部需要切換任務的地方,task的執(zhí)行就會暫停并釋放執(zhí)行線程給event loop,event loop接著會獲取下一個待執(zhí)行的coroutine,并進行相關(guān)的初始化之后,執(zhí)行這個task;
隨著event loop執(zhí)行完隊列中的最后一個coroutine才會切換到第一個coroutine;
隨著task的執(zhí)行結(jié)束,event loops會將task清除出隊列,對應的執(zhí)行結(jié)果會同步到future中,這個過程會持續(xù)到所有的task執(zhí)行結(jié)束;
三、順序執(zhí)行多個可重疊的任務
每個任務執(zhí)行中間會暫停給定的時間,循序執(zhí)行的時間就是每個任務執(zhí)行的時間加和;
import time def count_down(name, delay): indents = (ord(name) - ord('A')) * '\t' n = 3 while n: time.sleep(delay) duration = time.perf_counter() - start print('-' * 40) print(f'{duration:.4f} \t{indents}{name} = {n}') n -= 1 start = time.perf_counter() count_down('A', 1) count_down('B', 0.8) count_down('C', 0.5) print('-' * 40) print('Done') # ---------------------------------------- # 1.0010 A = 3 # ---------------------------------------- # 2.0019 A = 2 # ---------------------------------------- # 3.0030 A = 1 # ---------------------------------------- # 3.8040 B = 3 # ---------------------------------------- # 4.6050 B = 2 # ---------------------------------------- # 5.4059 B = 1 # ---------------------------------------- # 5.9065 C = 3 # ---------------------------------------- # 6.4072 C = 2 # ---------------------------------------- # 6.9078 C = 1 # ---------------------------------------- # Done
四、異步化同步代碼
python在語法上提供了async
、await
兩個關(guān)鍵字來簡化將同步代碼修改為異步;
async使用在函數(shù)的def關(guān)鍵字前邊,標記這是一個coroutine函數(shù);
await用在conroutine里邊,用于標記需要暫停釋放執(zhí)行流程給event loops;
await 后邊的表達式需要返回waitable的對象,例如conroutine、task、future等;
asyncio模塊主要提供了操作event loop的方式;
我們可以通過async將count_down標記為coroutine,然后使用await和asyncio.sleep來實現(xiàn)異步的暫停,從而將控制權(quán)交給event loop;
async def count_down(name, delay, start): indents = (ord(name) - ord('A')) * '\t' n = 3 while n: await asyncio.sleep(delay) duration = time.perf_counter() - start print('-' * 40) print(f'{duration:.4f} \t{indents}{name} = {n}') n -= 1
我們定義一個異步的main方法,主要完成task的創(chuàng)建和等待任務執(zhí)行結(jié)束;
async def main(): start = time.perf_counter() tasks = [asyncio.create_task(count_down(name,delay,start)) for name, delay in [('A', 1),('B', 0.8),('C', 0.5)]] await asyncio.wait(tasks) print('-' * 40) print('Done')
執(zhí)行我們可以看到時間已經(jīng)變?yōu)榱藞?zhí)行時間最長的任務的時間了;
asyncio.run(main()) # ---------------------------------------- # 0.5010 C = 3 # ---------------------------------------- # 0.8016 B = 3 # ---------------------------------------- # 1.0011 A = 3 # ---------------------------------------- # 1.0013 C = 2 # ---------------------------------------- # 1.5021 C = 1 # ---------------------------------------- # 1.6026 B = 2 # ---------------------------------------- # 2.0025 A = 2 # ---------------------------------------- # 2.4042 B = 1 # ---------------------------------------- # 3.0038 A = 1 # ---------------------------------------- # Done
五、使用多線程克服具體任務的異步限制
異步編程要求具體的任務必須是coroutine,也就是要求方法是異步的,否則只有任務執(zhí)行完了,才能將控制權(quán)釋放給event loop;
python中的concurent.futures提供了ThreadPoolExecutor和ProcessPoolExecutor,可以直接在異步編程中使用,從而可以在單獨的線程或者進程至今任務;
import time import asyncio from concurrent.futures import ThreadPoolExecutor def count_down(name, delay, start): indents = (ord(name) - ord('A')) * '\t' n = 3 while n: time.sleep(delay) duration = time.perf_counter() - start print('-'*40) print(f'{duration:.4f} \t{indents}{name} = {n}') n -=1 async def main(): start = time.perf_counter() loop = asyncio.get_running_loop() executor = ThreadPoolExecutor(max_workers=3) fs = [ loop.run_in_executor(executor, count_down, *args) for args in [('A', 1, start), ('B', 0.8, start), ('C', 0.5, start)] ] await asyncio.wait(fs) print('-'*40) print('Done.') asyncio.run(main()) # ---------------------------------------- # 0.5087 C = 3 # ---------------------------------------- # 0.8196 B = 3 # ---------------------------------------- # 1.0073 A = 3 # ---------------------------------------- # 1.0234 C = 2 # ---------------------------------------- # 1.5350 C = 1 # ---------------------------------------- # 1.6303 B = 2 # ---------------------------------------- # 2.0193 A = 2 # ---------------------------------------- # 2.4406 B = 1 # ---------------------------------------- # 3.0210 A = 1 # ---------------------------------------- # Done.
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
使用Python實現(xiàn) 學生學籍管理系統(tǒng)
這篇文章主要介紹了使用Python實現(xiàn) 學生學籍管理系統(tǒng),代碼大致分為五個函數(shù)組成,具體內(nèi)容詳情本文給大家介紹的非常詳細,需要的朋友可以參考下2019-11-11Python實現(xiàn)制度轉(zhuǎn)換(貨幣,溫度,長度)
這篇文章主要介紹了Python實現(xiàn)制度轉(zhuǎn)換(貨幣,溫度,長度),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-07-07Python 將RGB圖像轉(zhuǎn)換為Pytho灰度圖像的實例
下面小編就為大家?guī)硪黄狿ython 將RGB圖像轉(zhuǎn)換為Pytho灰度圖像的實例。具有很好的參考價值。希望對大家有所幫助。一起跟隨小編過來看看吧2017-11-11OpenCV實現(xiàn)從灰度圖像切出Mask前景區(qū)域
本文主要介紹了如何利用OpenCV實現(xiàn)從灰度圖像,根據(jù)閾值,切出多個前景區(qū)域,過濾面積太小的圖像。文中的示例代碼講解詳細,需要的可以參考一下2022-06-06