協程與多進程的完美結合
我們知道,協程本質上是單線程單進程,通過充分利用IO等待時間來實現高并發。在IO等待時間之外的代碼,還是串行運行的。因此,如果協程非常多,多少每個協程內部的串行代碼運行時間超過了IO請求的等待時間,那么它的并發就會有一個上限。
舉個例子,電飯煲煮飯,洗衣機洗衣服,熱水壺燒水,他們都是啟動設備以后就能自己運行,我們可以利用他們自己運行的時間,讓這三件事情看起來幾乎在同時進行。但如果除了這三件事情外,還有開電視,開空調,發微信……等等幾十個事情。每個事情單獨拿出來確實都只需要做個開頭,剩下的就是等,但由于做這個開頭也需要時間,因此把他們全部啟動起來也要不少時間,你的效率還是被卡住。
現在,如果有兩個人一起來做這些事情,那情況就不一樣了。一個人煮飯和燒水,另一個人開洗衣機,開電視和空調。效率進一步提升。
這就是協程與多進程的結合,每個進程里面多個協程同時運行,充分利用CPU的每一個核心,又充分利用了IO等待時間,把CPU跑滿,把網絡帶寬跑滿。強強聯合,速度更快。
有一個第三方庫aiomultiprocess,讓你能用幾行代碼就實現多進程與協程的組合。
首先使用pip安裝:
python3 -m pip install aiomultiprocess
它的語法非常簡單:
from aiomultiprocess import Pool
async with Pool() as pool:
results = await pool.map(協程, 參數列表)
只需要3行代碼,它就會在你CPU上每個核啟動一個進程,每個進程中不停啟動協程。
我們來寫一段實際代碼:
import asyncio
import httpx
from aiomultiprocess import Pool
async def get(url):
async with httpx.AsyncClient() as client:
resp = await client.get(url)
return resp.text
async def main():
urls = [url1, url2, url3]
async with Pool() as pool:
async for result in pool.map(get, urls):
print(result) # 每一個URL返回的內容
if __name__ == '__main__':
asyncio.run(main())
之前我寫異步協程文章的時候,有些人同學會問我,爬蟲的速度真的那么重要嗎?難道不是突破反爬蟲最重要嗎?
我的回答是,不要看到用aiohttp請求網址就覺得是做爬蟲。在微服務里面,自己請求自己的HTTP接口,也需要使用httpx或者aiohttp。在這樣的場景里面,速度就是非常的重要,有時候就是需要做到越快越好。