Python關(guān)鍵字?asynico基本用法
Python異步編程之Asyncio
1. 協(xié)程簡介
1.1 協(xié)程的含義及實(shí)現(xiàn)方法
協(xié)程(Coroutine),也可以被稱為微線程,是一種用戶態(tài)內(nèi)的上下文切換技術(shù)。簡而言之,其實(shí)就是通過一個(gè)線程實(shí)現(xiàn)代碼塊相互切換執(zhí)行。例如:
def func1(): print(1) ... # 協(xié)程介入 print(2) def func2(): print(3) ... # 協(xié)程介入 print(4) func1() func2()
上述代碼是普通的函數(shù)定義和執(zhí)行,按流程分別執(zhí)行兩個(gè)函數(shù)中的代碼,并先后會輸出:1、2、3、4
。但如果介入?yún)f(xié)程技術(shù)那么就可以實(shí)現(xiàn)函數(shù)見代碼切換執(zhí)行,最終輸入:1、3、2、4
。
在Python中有多種方式可以實(shí)現(xiàn)協(xié)程,例如:
- greenlet,是一個(gè)第三方模塊,用于實(shí)現(xiàn)協(xié)程代碼(Gevent協(xié)程就是基于greenlet實(shí)現(xiàn));
- yield,生成器,借助生成器的特點(diǎn)也可以實(shí)現(xiàn)協(xié)程代碼;
- asyncio,在Python3.4中引入的模塊用于編寫協(xié)程代碼;
- async & awiat,在Python3.5中引入的兩個(gè)關(guān)鍵字,結(jié)合asyncio模塊可以更方便的編寫協(xié)程代碼。
前兩種實(shí)現(xiàn)方式較為老舊,所以重點(diǎn)關(guān)注后面的方式
標(biāo)準(zhǔn)庫實(shí)現(xiàn)方法
asyncio是Python 3.4版本引入的標(biāo)準(zhǔn)庫,直接內(nèi)置了對異步IO的支持。
import asyncio @asyncio.coroutine def func1(): print(1) yield from asyncio.sleep(2) # 遇到IO耗時(shí)操作,自動化切換到tasks中的其他任務(wù) print(2) @asyncio.coroutine def func2(): print(3) yield from asyncio.sleep(2) # 遇到IO耗時(shí)操作,自動化切換到tasks中的其他任務(wù) print(4) tasks = [ asyncio.ensure_future( func1() ), asyncio.ensure_future( func2() ) ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
關(guān)鍵字實(shí)現(xiàn)方法
async & await
關(guān)鍵字在Python3.5版本中正式引入,代替了asyncio.coroutine
裝飾器,基于他編寫的協(xié)程代碼其實(shí)就是上一示例的加強(qiáng)版,讓代碼可以更加簡便可讀。
import asyncio async def func1(): print(1) await asyncio.sleep(2) # 耗時(shí)操作 print(2) async def func2(): print(3) await asyncio.sleep(2) # 耗時(shí)操作 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 案例演示
例如:用代碼實(shí)現(xiàn)下載 url_list
中的圖片。
方式一:同步編程實(shí)現(xiàn)
# requests庫僅支持同步的http網(wǎng)絡(luò)請求 import requests def download_image(url): print("開始下載:",url) # 發(fā)送網(wǎng)絡(luò)請求,下載圖片 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)
輸出:按順序發(fā)送請求,請求一次下載一張圖片,假如每次下載花費(fèi)1s,完成任務(wù)需要3s 以上。
方式二:基于協(xié)程的程實(shí)現(xiàn)
# aiohttp 為支持異步編程的http請求庫 import aiohttp import asyncio async def fetch(session, url): print("發(fā)送請求:", 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())
輸出:一次發(fā)送三個(gè)下載請求,同時(shí)下載,假如每次下載花費(fèi)1s,完成任務(wù)僅需要1s 左右,第一種方法的耗時(shí)為第二種的三倍。
1.3 小結(jié)
協(xié)程可以讓原來要使用異步+回調(diào)方式寫的非人類代碼,用看似同步的方式寫出來。
2. 異步編程簡介
2.1 同步和異步的區(qū)別
同步 :循序漸進(jìn)執(zhí)行操作、請求 異步:無需等待上一步操作、請求完成,就開始下一步(每個(gè)操作仍然有先后順序)
目前python異步相關(guān)的主流技術(shù)是通過包含關(guān)鍵字async&await的async模塊實(shí)現(xiàn)。
2.2 異步編程-事件循環(huán)
事件循環(huán),可以把他當(dāng)做是一個(gè)while循環(huán),這個(gè)while循環(huán)在周期性的運(yùn)行并執(zhí)行一些任務(wù),在特定條件下終止循環(huán)。
# 偽代碼 任務(wù)列表 = [ 任務(wù)1, 任務(wù)2, 任務(wù)3,... ] while True: 可執(zhí)行的任務(wù)列表,已完成的任務(wù)列表 = 去任務(wù)列表中檢查所有的任務(wù),將'可執(zhí)行'和'已完成'的任務(wù)返回 for 就緒任務(wù) in 已準(zhǔn)備就緒的任務(wù)列表: 執(zhí)行已就緒的任務(wù) for 已完成的任務(wù) in 已完成的任務(wù)列表: 在任務(wù)列表中移除 已完成的任 如果 任務(wù)列表 中的任務(wù)都已完成,則終止循環(huán)
在編寫程序時(shí)候可以通過如下代碼來獲取和創(chuàng)建事件循環(huán)。
# 方式一: import asyncio # 生成或獲取一個(gè)事件循環(huán) loop = asyncio.get_event_loop() # 將任務(wù)添加到事件循環(huán)中 loop.run_until_complete(任務(wù)) # 方式二(python3.7及以上版本支持): asyncio.run( 任務(wù) )
2.3 異步編程-快速上手
async 關(guān)鍵字
- 協(xié)程函數(shù):定義函數(shù)時(shí)候由async關(guān)鍵字裝飾的函數(shù)
async def 函數(shù)名
- 協(xié)程對象:執(zhí)行協(xié)程函數(shù)得到的協(xié)程對象。
# 協(xié)程函數(shù) async def func(): pass # 協(xié)程對象 result = func()
注意:執(zhí)行協(xié)程函數(shù)只會創(chuàng)建協(xié)程對象,函數(shù)內(nèi)部代碼不會執(zhí)行。如果想要運(yùn)行協(xié)程函數(shù)內(nèi)部代碼,必須要將協(xié)程對象交給事件循環(huán)來處理。
import asyncio async def func(): print("執(zhí)行協(xié)程函數(shù)內(nèi)部代碼!") result = func() # 調(diào)用方法1: # loop = asyncio.get_event_loop() # loop.run_until_complete( result ) # 調(diào)用方法2: asyncio.run( result )
await 關(guān)鍵字
await + 可等待的對象(協(xié)程對象、Future、Task對象 -> IO等待),遇到IO操作掛起當(dāng)前協(xié)程(任務(wù)),等IO操作完成之后再繼續(xù)往下執(zhí)行。當(dāng)前協(xié)程掛起時(shí),事件循環(huán)可以去執(zhí)行其他協(xié)程(任務(wù))。
import asyncio async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值' async def func(): print("執(zhí)行協(xié)程函數(shù)內(nèi)部代碼") # await等待對象的值得到結(jié)果之后再繼續(xù)向下走 response = await others() print("IO請求結(jié)束,結(jié)果為:", response) asyncio.run( func() )
Task 對象
Task對象的作用是在事件循環(huán)中添加多個(gè)任務(wù),用于并發(fā)調(diào)度協(xié)程,通過asyncio.create_task(協(xié)程對象)
的方式創(chuàng)建Task對象,這樣可以讓協(xié)程加入事件循環(huán)中等待被調(diào)度執(zhí)行。
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 案例演示
例如:用代碼實(shí)現(xiàn)連接并查詢數(shù)據(jù)庫的同時(shí),下載一個(gè)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: # 網(wǎng)絡(luò)IO請求,獲取響應(yīng) async with session.get(url)as res: if res.status == 200: print("下載成功", res) # 磁盤IO請求,讀取響應(yīng)數(shù)據(jù) apk = await res.content.read() async with aiofiles.open("demo2.apk", "wb") as f: # 磁盤IO請求,數(shù)據(jù)寫入本地磁盤 await f.write(apk) else: print("下載失敗") async def excute_sql(sql): # 網(wǎng)絡(luò)IO操作:連接MySQL conn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='123', db='mysql', ) # 網(wǎng)絡(luò)IO操作:創(chuàng)建CURSOR cur = await conn.cursor() # 網(wǎng)絡(luò)IO操作:執(zhí)行SQL await cur.execute(sql) # 網(wǎng)絡(luò)IO操作:獲取SQL結(jié)果 result = await cur.fetchall() print(result) # 網(wǎng)絡(luò)IO操作:關(guān)閉鏈接 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()
創(chuàng)建了事件循環(huán)。wait()
方法將task任務(wù)列表加入到當(dāng)前的事件循環(huán)中;(注意:必須先創(chuàng)建事件循環(huán),后加入任務(wù)列表,否則會報(bào)錯(cuò))
【step2】事件循環(huán)監(jiān)聽事件狀態(tài),開始執(zhí)行代碼,先執(zhí)行列表中的get_app()
方法,當(dāng)代碼執(zhí)行到async with session.get(url)as res:
時(shí),遇到await關(guān)鍵字表示有IO耗時(shí)操作,線程會將該任務(wù)掛起在后臺執(zhí)行,并切換到另外一個(gè)異步函數(shù)excute_sql()
;
【step3】當(dāng)代碼執(zhí)行到excute_sql()
的第一個(gè)IO耗時(shí)操作后,線程會重復(fù)先前的操作,將該任務(wù)掛起,去執(zhí)行其他可執(zhí)行代碼。假如此時(shí)事件循環(huán)監(jiān)聽到get_app()
中的第一IO耗時(shí)操作已經(jīng)執(zhí)行完成,那么線程會切換到該方法第一個(gè)IO操作后的代碼,并按順序執(zhí)行直到遇上下一個(gè)await裝飾的IO操作;假如事件循環(huán)監(jiān)聽到excute_sql()
中的第一個(gè)IO操作先于get_app()
的第一個(gè)IO操作完成,那么線程會繼續(xù)執(zhí)行excute_sql
的后續(xù)代碼;
【step4】線程會重復(fù)進(jìn)行上述第3點(diǎn)中的步驟,直到代碼全部執(zhí)行完成,事件循環(huán)也會隨之停止。
2.5 小結(jié)
一般來說CPU的耗時(shí)運(yùn)算方式有:
計(jì)算密集型的操作:計(jì)算密集型任務(wù)的特點(diǎn)是要進(jìn)行大量的計(jì)算、邏輯判斷,消耗CPU資源,比如計(jì)算圓周率、對視頻進(jìn)行高清解碼等等。
IO密集型的操作:涉及到網(wǎng)絡(luò)、磁盤IO的任務(wù)都是IO密集型任務(wù),這類任務(wù)的特點(diǎn)是CPU消耗很少,任務(wù)的大部分時(shí)間都在等待IO操作完成(因?yàn)镮O的速度遠(yuǎn)遠(yuǎn)低于CPU和內(nèi)存的速度)。
異步編程基于協(xié)程實(shí)現(xiàn),如果利用協(xié)程實(shí)現(xiàn)計(jì)算密集型操作,因?yàn)榫€程在上下文之間的來回切換總會經(jīng)歷類似于”計(jì)算“-->”保存“-->”創(chuàng)建新環(huán)境“ 的一系列操作,導(dǎo)致系統(tǒng)的整體性能反而會下降。所以異步編程并不適用于計(jì)算密集型的程序。然而在IO密集型操作匯總,協(xié)程在IO等待時(shí)間就去切換執(zhí)行其他任務(wù),當(dāng)IO操作結(jié)束后再自動回調(diào),那么就會大大節(jié)省資源并提供性能。
好了,以上內(nèi)容就給大家介紹到這里,下面再次補(bǔ)充介紹Python關(guān)鍵字 asynico知識。
Python關(guān)鍵字 asynico
同步和異步
同步和異步是指程序的執(zhí)行方式。在同步執(zhí)行中,程序會按順序一個(gè)接一個(gè)地執(zhí)行任務(wù),直到當(dāng)前任務(wù)完成。而在異步執(zhí)行中,程序會在等待當(dāng)前任務(wù)完成的同時(shí),執(zhí)行其他任務(wù)。
同步執(zhí)行意味著程序會阻塞,等待任務(wù)完成,而異步執(zhí)行則意味著程序不會阻塞,可以同時(shí)執(zhí)行多個(gè)任務(wù)。
同步和異步的選擇取決于你的程序需求。如果你的程序需要等待某些任務(wù)完成后才能繼續(xù),那么同步的方式可能是更好的選擇。如果你的程序可以在等待任務(wù)完成的同時(shí)繼續(xù)執(zhí)行其他任務(wù),那么異步的方式可能是更好的選擇。
asyncio
asyncio是Python的異步編程庫,用于編寫并發(fā)程序。它提供了一組基于協(xié)程的工具,可以幫助你實(shí)現(xiàn)異步網(wǎng)絡(luò)通信、并發(fā)計(jì)算等任務(wù)。
舉個(gè)例子,假設(shè)你編寫了一個(gè)程序,要向多個(gè)遠(yuǎn)程服務(wù)器發(fā)送請求,然后等待這些服務(wù)器的響應(yīng)。如果你使用同步的方式編寫程序,你可能會這樣寫:
import requests def send_request(url): response = requests.get(url) return response.text # 向服務(wù)器1發(fā)送請求 response1 = send_request("http://server1.com") # 向服務(wù)器2發(fā)送請求 response2 = send_request("http://server2.com") # 向服務(wù)器3發(fā)送請求 response3 = send_request("http://server3.com")
在這段代碼中,你會發(fā)現(xiàn),當(dāng)你向服務(wù)器1發(fā)送請求時(shí),程序會等待服務(wù)器1的響應(yīng),然后再向服務(wù)器2發(fā)送請求,最后再向服務(wù)器3發(fā)送請求。這意味著,當(dāng)你發(fā)送請求時(shí),程序都會被阻塞,直到收到響應(yīng)。
如果你使用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() # 向服務(wù)器1發(fā)送請求 response1 = asyncio.run(send_request("http://server1.com")) # 向服務(wù)器2發(fā)送請求 response2 = asyncio.run(send_request("http://server2.com")) # 向服務(wù)器3發(fā)送請求 response3 = asyncio.run(send_request("http://server3.com"))
在這段代碼中,你會發(fā)現(xiàn),當(dāng)你向服務(wù)器1發(fā)送請求時(shí),程序不會立刻等待服務(wù)器1的響應(yīng)。相反,程序會立刻向服務(wù)器2和服務(wù)器3發(fā)送請求,然后等待所有響應(yīng)的到來。這意味著,當(dāng)你發(fā)送請求時(shí),程序不會阻塞,而是會繼續(xù)執(zhí)行其他任務(wù)。
這就是asyncio的基本用法,它可以幫助你編寫高效的異步程序。
Github主頁:https://github.com/Viceversa0 發(fā)布一些感覺有用的代碼。
到此這篇關(guān)于Python關(guān)鍵字 asynico基本用法的文章就介紹到這了,更多相關(guān)Python關(guān)鍵字 asynico內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Django框架創(chuàng)建項(xiàng)目的方法入門教程
這篇文章主要介紹了Django框架創(chuàng)建項(xiàng)目的方法,結(jié)合實(shí)例形式分析了Django框架管理工具的使用及創(chuàng)建項(xiàng)目的相關(guān)操作技巧,需要的朋友可以參考下2019-11-11Python3.6實(shí)現(xiàn)帶有簡單界面的有道翻譯小程序
本文通過實(shí)例代碼給大家介紹了基于Python3.6實(shí)現(xiàn)帶有簡單界面的有道翻譯小程序,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2019-04-04pycharm工具連接mysql數(shù)據(jù)庫失敗問題
這篇文章主要介紹了pycharm工具連接mysql數(shù)據(jù)庫失敗問題及解決方法,非常不錯(cuò)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04基于TensorFlow常量、序列以及隨機(jī)值生成實(shí)例
今天小編就為大家分享一篇基于TensorFlow常量、序列以及隨機(jī)值生成實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01在Ubuntu系統(tǒng)下安裝使用Python的GUI工具wxPython
這篇文章主要介紹了在Ubuntu系統(tǒng)下安裝使用Python的GUI工具wxPython的方法,wxPython可以為Python提供強(qiáng)大的圖形化界面開發(fā)支持,需要的朋友可以參考下2016-02-02基于 Python實(shí)現(xiàn)云服務(wù)器的CDN域名遠(yuǎn)程鑒權(quán)配置
這篇文章主要介紹了基于 Python實(shí)現(xiàn)云服務(wù)器的CDN域名遠(yuǎn)程鑒權(quán)配置,文章內(nèi)容技術(shù)詳細(xì),具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05在Windows系統(tǒng)上搭建Nginx+Python+MySQL環(huán)境的教程
這篇文章主要介紹了在Windows系統(tǒng)上搭建Nginx+Python+MySQL環(huán)境的教程,文中使用flup中間件及FastCGI方式連接,需要的朋友可以參考下2015-12-12