最近正在學習Python中的異步編程,看了一些博客後作了一些小測驗:對比asyncio+aiohttp的爬蟲和asyncio+aiohttp+concurrent.futures(線程池/進程池)在效率中的差別,註釋:在爬蟲中我幾乎沒有使用任何計算性任務,爲了探測異步的性能,所有都只是作了網絡IO請求,就是說aiohttp把網頁get完就程序就done了。html
結果發現前者的效率比後者還要高。我詢問了另一位博主,(提供代碼的博主沒回我信息),他說使用concurrent.futures的話由於我所有都是IO任務,若是把這些IO任務分散到線程池/進程池,反而多線程/多進程之間的切換開銷還會下降爬蟲的效率。我想了想的確如此。node
那麼個人問題是:僅僅在爬取網頁的過程當中,就是request.get部分,多線程確定是沒有存在的必要了,由於GIL這個大坑,進程池可能好點,可是性能仍是不如異步爬蟲,並且更加浪費資源。既然這樣,是否是之後在爬蟲的爬取網頁階段咱們徹底均可以用興起的asyncio+aiohttp代替。(以及其餘IO任務好比數據庫/文件讀寫)git
固然在數據處理階段仍是要採用多進程,可是我以爲多線程是完全沒用了,本來它相比多進程的優點在於IO型任務,現看來在它的優點徹底被異步取代了。(固然問題創建在不考慮兼容2.x)github
注:還有一個額外的問題就是,看到一些博客說requests庫不支持異步編程是什麼意思,爲了充分發回異步的優點應該使用aiohttp,我沒有看過requests的源代碼,可是一些結果顯示aiohttp的性能確實更好,各位網友能解釋一下嗎?數據庫
asyncio+aiohttp編程
import aiohttp async def fetch_async(a): async with aiohttp.request('GET', URL.format(a)) as r: data = await r.json() return data['args']['a'] start = time.time() event_loop = asyncio.get_event_loop() tasks = [fetch_async(num) for num in NUMBERS] results = event_loop.run_until_complete(asyncio.gather(*tasks)) for num, result in zip(NUMBERS, results): print('fetch({}) = {}'.format(num, result))
asyncio+aiohttp+線程池比上面要慢1秒json
async def fetch_async(a): async with aiohttp.request('GET', URL.format(a)) as r: data = await r.json() return a, data['args']['a'] def sub_loop(numbers): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) tasks = [fetch_async(num) for num in numbers] results = loop.run_until_complete(asyncio.gather(*tasks)) for num, result in results: print('fetch({}) = {}'.format(num, result)) async def run(executor, numbers): await asyncio.get_event_loop().run_in_executor(executor, sub_loop, numbers) def chunks(l, size): n = math.ceil(len(l) / size) for i in range(0, len(l), n): yield l[i:i + n] event_loop = asyncio.get_event_loop() tasks = [run(executor, chunked) for chunked in chunks(NUMBERS, 3)] results = event_loop.run_until_complete(asyncio.gather(*tasks)) print('Use asyncio+aiohttp+ThreadPoolExecutor cost: {}'.format(time.time() - start))
傳統的requests + ThreadPoolExecutor比上面慢了3倍segmentfault
import time import requests from concurrent.futures import ThreadPoolExecutor NUMBERS = range(12) URL = 'http://httpbin.org/get?a={}' def fetch(a): r = requests.get(URL.format(a)) return r.json()['args']['a'] start = time.time() with ThreadPoolExecutor(max_workers=3) as executor: for num, result in zip(NUMBERS, executor.map(fetch, NUMBERS)): print('fetch({}) = {}'.format(num, result)) print('Use requests+ThreadPoolExecutor cost: {}'.format(time.time() - start))
以上問題創建在CPython,至於我喜歡用多線程,不喜歡協程風格這類型的回答顯然不屬於本題討論範疇。我主要想請教的是:
若是Python拿不下GIL,我認爲將來理想的模型應該是多進程 + 協程(asyncio+aiohttp)。uvloop和sanic以及500lines一個爬蟲項目已經開始這麼幹了。不討論兼容型問題,上面的見解是否正確,有一些什麼場景協程沒法取代多線程。網絡
異步有不少方案,twisted, tornado等都有本身的解決方案,問題創建在asyncio+aiohttp的協程異步。多線程
還有一個問題也想向各位網友請教一下