本文首發於知乎
異步是繼多線程、多進程以後第三種實現併發的方式,主要用於IO密集型任務的運行效率提高。python中的異步基於yield
生成器,在講解這部分原理以前,咱們先學會異步庫asyncio的使用。html
本文主要講解asyncio
模塊的通用性問題,對一些函數細節的使用就簡單略過。python
本文分爲以下部分編程
import asyncio
async def myfun(i):
print('start {}th'.format(i))
await asyncio.sleep(1)
print('finish {}th'.format(i))
loop = asyncio.get_event_loop()
myfun_list = (myfun(i) for i in range(10))
loop.run_until_complete(asyncio.gather(*myfun_list))
複製代碼
這樣運行,10次等待總共只等待了1秒。網絡
上面代碼一些約定俗成的用法記住就好,如session
async
上面是第一種常見的用法,下面是另一種多線程
import asyncio
async def myfun(i):
print('start {}th'.format(i))
await asyncio.sleep(1)
print('finish {}th'.format(i))
loop = asyncio.get_event_loop()
myfun_list = [asyncio.ensure_future(myfun(i)) for i in range(10)]
loop.run_until_complete(asyncio.wait(myfun_list))
複製代碼
這種用法和上面一種的不一樣在於後面調用的是asyncio.gather
仍是asyncio.wait
,當前當作徹底等價便可,因此平時使用用上面哪一種均可以。併發
上面是最常看到的兩種使用方式,這裏列出來保證讀者在看其餘文章時不會發蒙。app
另外,兩者實際上是有細微差異的異步
gather
更擅長於將函數聚合在一塊兒wait
更擅長篩選運行情況細節能夠參考這篇回答async
與以前學過的多線程、多進程相比,asyncio
模塊有一個很是大的不一樣:傳入的函數不是爲所欲爲
myfun
函數中的sleep
換成time.sleep(1)
,運行時則不是異步的,而是同步,共等待了10秒myfun
,好比換成下面這個使用request
抓取網頁的函數import asyncio
import requests
from bs4 import BeautifulSoup
async def get_title(a):
url = 'https://movie.douban.com/top250?start={}&filter='.format(a*25)
r = requests.get(url)
soup = BeautifulSoup(r.content, 'html.parser')
lis = soup.find('ol', class_='grid_view').find_all('li')
for li in lis:
title = li.find('span', class_="title").text
print(title)
loop = asyncio.get_event_loop()
fun_list = (get_title(i) for i in range(10))
loop.run_until_complete(asyncio.gather(*fun_list))
複製代碼
依然不會異步執行。
到這裏咱們就會想,是否是異步只對它本身定義的sleep
(await asyncio.sleep(1)
)才能觸發異步?
對於上述函數,asyncio
庫只能經過添加線程的方式實現異步,下面咱們實現time.sleep
時的異步
import asyncio
import time
def myfun(i):
print('start {}th'.format(i))
time.sleep(1)
print('finish {}th'.format(i))
async def main():
loop = asyncio.get_event_loop()
futures = (
loop.run_in_executor(
None,
myfun,
i)
for i in range(10)
)
for result in await asyncio.gather(*futures):
pass
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
複製代碼
上面run_in_executor
其實開啓了新的線程,再協調各個線程。調用過程比較複雜,只要當模板同樣套用便可。
上面10次循環仍然不是一次性打印出來的,而是像分批次同樣打印出來的。這是由於開啓的線程不夠多,若是想要實現一次打印,能夠開啓10個線程,代碼以下
import concurrent.futures as cf # 多加一個模塊
import asyncio
import time
def myfun(i):
print('start {}th'.format(i))
time.sleep(1)
print('finish {}th'.format(i))
async def main():
with cf.ThreadPoolExecutor(max_workers = 10) as executor: # 設置10個線程
loop = asyncio.get_event_loop()
futures = (
loop.run_in_executor(
executor, # 按照10個線程來執行
myfun,
i)
for i in range(10)
)
for result in await asyncio.gather(*futures):
pass
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
複製代碼
用這種方法實現requests
異步爬蟲代碼以下
import concurrent.futures as cf
import asyncio
import requests
from bs4 import BeautifulSoup
def get_title(i):
url = 'https://movie.douban.com/top250?start={}&filter='.format(i*25)
r = requests.get(url)
soup = BeautifulSoup(r.content, 'html.parser')
lis = soup.find('ol', class_='grid_view').find_all('li')
for li in lis:
title = li.find('span', class_="title").text
print(title)
async def main():
with cf.ThreadPoolExecutor(max_workers = 10) as executor:
loop = asyncio.get_event_loop()
futures = (
loop.run_in_executor(
executor,
get_title,
i)
for i in range(10)
)
for result in await asyncio.gather(*futures):
pass
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
複製代碼
這種開啓多個線程的方式也算異步的一種,下面一節詳細解釋。
如今咱們講了一些異步的使用,是時候解釋一些概念了
await
只使用一個線程就能夠實現任務切換,另外一種是開啓了多個線程,經過線程調度實現異步上面咱們是經過開啓多個線程來實現requests
的異步,若是咱們想只用一個線程(用await
),就要換一個網頁請求函數。
事實上要想用await
,必須是一個awaitable對象,這是不能使用requests
的緣由。而轉化成awaitable對象這樣的事固然也不用咱們本身實現,如今有一個aiohttp
模塊能夠將網頁請求和asyncio
模塊完美對接。使用這個模塊改寫代碼以下
import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def get_title(i):
url = 'https://movie.douban.com/top250?start={}&filter='.format(i*25)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(resp.status)
text = await resp.text()
print('start', i)
soup = BeautifulSoup(text, 'html.parser')
lis = soup.find('ol', class_='grid_view').find_all('li')
for li in lis:
title = li.find('span', class_="title").text
print(title)
loop = asyncio.get_event_loop()
fun_list = (get_title(i) for i in range(10))
loop.run_until_complete(asyncio.gather(*fun_list))
複製代碼
專欄主頁:python編程
專欄目錄:目錄
版本說明:軟件及包版本說明