4、协程与asyncio异步框架
目錄
一、概念
二、使用場景
三、同步與異步的區別
四、協程實現方式
1.greenlet實現協程
2.yield關鍵字
3.asyncio模塊(異步爬蟲)
zip函數
4.事件循環
5.uvloop事件循環性能提升
6.await與async關鍵字
五、asyncio + 不支持異步的模塊
一、概念
協程(Coroutine),也可以被稱為微線程,是一種用戶態內的上下文切換技術。基于單線程來實現并發,即只用一個主線程(可利用的cpu只有一個)情況下實現并發,并發的本質:切換+保存狀態。
協程的目的,想要在單線程下實現并發,這里的并發指的是多個任務看起來是同時運行的。
缺點:無法利用CPU多核,一旦協程出現阻塞,將會阻塞整個線程。
二、使用場景
單純地切換反而會降低代碼運行效率,協程適用于網絡請求,io阻塞異步場景。
三、同步與異步的區別
同步,是所有的操作都做完,才返回給用戶結果。即寫完數據庫之后,在相應用戶,用戶體驗不好。同步程序的瓶頸在于漫長的I/O等待,想要提高程序效率必須減少I/O等待時間,從提高程序的局部性著手。
異步,不用等所有操作等做完,就相應用戶請求。即先相應用戶請求,然后慢慢去寫數據庫,用戶體驗較好。
四、協程實現方式
1.greenlet實現協程
greenlet、gevent早期模塊(了解即可)
遇到IO阻塞時會自動切換任務
from greenlet import greenletdef func1():print(1) # 第1步:輸出 1gr2.switch() # 第3步:切換到 func2 函數print(2) # 第6步:輸出 2gr2.switch() # 第7步:切換到 func2 函數,從上一次執行的位置繼續向后執行def func2():print(3) # 第4步:輸出 3gr1.switch() # 第5步:切換到 func1 函數,從上一次執行的位置繼續向后執行print(4) # 第8步:輸出 4gr1 = greenlet(func1) gr2 = greenlet(func2)gr1.switch() # 第1步:去執行 func1 函數 import gevent def eat(name):print('%s eat 1' %name)gevent.sleep(2)print('%s eat 2' %name)def play(name):print('%s play 1' %name)gevent.sleep(1)print('%s play 2' %name)g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,name='egon') g1.join() g2.join() #或者gevent.joinall([g1,g2]) print('執行完成')2.yield關鍵字
實際開發基本用不到(了解即可),基本都是用封裝好的框架
def func1():yield 1yield from func2()yield 2def func2():yield 3yield 4f1 = func1() for item in f1:print(item)3.asyncio模塊(異步爬蟲)
aiohttp:Client Quickstart — aiohttp 3.8.4 documentationa
aiohttp發送post請求
async with session.post(url, data=json.dumps(params), headers=headers, verify_ssl=False) as response:output = await response.json() import aiohttp import asyncio import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())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)print('下載完成',url)async def main():async with aiohttp.ClientSession() as session:url_list = ['https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg','https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg','https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg']tasks = [ asyncio.create_task(fetch(session, url)) for url in url_list ]await asyncio.wait(tasks)if __name__ == '__main__':asyncio.run( main() )之前舊的寫法
import aiohttp import asyncioasync 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)print('下載完成', url)async def main():async with aiohttp.ClientSession() as session:url_list = ['https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg','https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg','https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg']tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]await asyncio.wait(tasks)if __name__ == '__main__':loop = asyncio.get_event_loop()loop.run_until_complete(main())# asyncio.run( result ) # python3.7zip函數
zip()?函數用于將可迭代的對象作為參數,將對象中對應的元素打包成一個個元組,然后返回由這些元組組成的列表。Python zip() 函數 | 菜鳥教程
import asyncioasync def download(url, t):print('開始下載', url, t)await asyncio.sleep(1)print('下載完成', url, t)async def main():urls = ['http://www.baidu.com/','http://www.weiwei.com/','http://www.weihubj.shop']ts = [1, 2, 3]# task列表tasks = [asyncio.create_task(download(url, t)) for url, t in zip(urls, ts)]# 等待任務都結束await asyncio.wait(tasks)if __name__ == '__main__':asyncio.run(main())4.事件循環
注意python3.7之后,直接ayncio.run就行,里面封裝了事件loop
import asyncioasync def func():print("開始執行")result = func()loop = asyncio.get_event_loop() loop.run_until_complete(result) # asyncio.run( result ) # python3.75.uvloop事件循環性能提升
是asyncio的事件循環的替代方案。事件循環 > 默認asyncio的事件循環。性能提升,重點
import asyncio import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())# 編寫asyncio的代碼,與之前寫的代碼一致。# 內部的事件循環自動化會變為uvloop asyncio.run(...)6.await與async關鍵字
await + 可等待的對象(協程對象、Future、Task對象 -> IO等待)
Tasks用于并發調度協程,通過asyncio.create_task(協程對象)的方式創建Task對象
Task繼承Future,Task對象內部await結果的處理基于Future對象來的。(Future對象了解即可)
import asyncioasync def others():print("start")await asyncio.sleep(2)print('end')return '返回值'async def func():print("執行協程函數內部代碼")# 遇到IO操作掛起當前協程(任務),等IO操作完成之后再繼續往下執行。當前協程掛起時,事件循環可以去執行其他協程(任務)。response1 = await others()print("IO請求結束,結果為:", response1)response2 = await others()print("IO請求結束,結果為:", response2)asyncio.run( func() )函數名前面加上關鍵字async,表示協程函數;協程函數()不會執行,表示一個協程對象。
如func()表示一個協程對象,不會去執行函數。
五、asyncio + 不支持異步的模塊
效果和協程是一樣的,但是線程等待耗費的資源更多,不得已建議才用這種混合的方式
這種混合使用的還不如直接用線程池去做(個人感覺)
requests只能發送同步請求;aiohttp只能發送異步請求;httpx既能發送同步請求
import asyncio import requests import json import uuid import timeimport uvloopasyncio.set_event_loop_policy(uvloop.EventLoopPolicy())def func(query, name):print(name)url = "http://httpbin.org/post"data = {'name': query}headers = {'content-type': 'application/json','User-Agent': "Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 96.0.4664 .93 Safari / 537.36",}response = requests.post(url=url, data=json.dumps(data), headers=headers)return response.textasync def send_post(query):print("開始發送請求", query)loop = asyncio.get_event_loop()# query,name是傳給func函數的參數,name = 'weige'future_obj = loop.run_in_executor(None, func, query, name)response = await future_objprint('請求完成')print(response)if __name__ == '__main__':start_time = time.time()query_list = ['kongtiao', 'xiyiji', 'yifu']tasks = [send_post(query) for query in query_list]loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))end_time = time.time()print('執行時間', end_time - start_time)總結
以上是生活随笔為你收集整理的4、协程与asyncio异步框架的全部內容,希望文章能夠幫你解決所遇到的問題。