>  Q&A  >  본문

node.js - Python有了asyncio和aiohttp在爬虫这类型IO任务中多线程/多进程还有存在的必要吗?

最近正在学习Python中的异步编程,看了一些博客后做了一些小测验:对比asyncio+aiohttp的爬虫和asyncio+aiohttp+concurrent.futures(线程池/进程池)在效率中的差异,注释:在爬虫中我几乎没有使用任何计算性任务,为了探测异步的性能,全部都只是做了网络IO请求,就是说aiohttp把网页get完就程序就done了。

结果发现前者的效率比后者还要高。我询问了另外一位博主,(提供代码的博主没回我信息),他说使用concurrent.futures的话因为我全部都是IO任务,如果把这些IO任务分散到线程池/进程池,反而多线程/多进程之间的切换开销还会降低爬虫的效率。我想了想的确如此。

那么我的问题是:仅仅在爬取网页的过程中,就是request.get部分,多线程肯定是没有存在的必要了,因为GIL这个大坑,进程池可能好点,但是性能还是不如异步爬虫,而且更加浪费资源。既然这样,是不是以后在爬虫的爬取网页阶段我们完全都可以用兴起的asyncio+aiohttp代替。(以及其他IO任务比如数据库/文件读写)

当然在数据处理阶段还是要采用多进程,但是我觉得多线程是彻底没用了,原本它相比多进程的优势在于IO型任务,现看来在它的优势完全被异步取代了。(当然问题建立在不考虑兼容2.x)

注:还有一个额外的问题就是,看到一些博客说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秒

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倍

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的协程异步。

还有一个问题也想向各位网友请教一下

阿神阿神2714일 전1078

모든 응답(6)나는 대답할 것이다

  • 伊谢尔伦

    伊谢尔伦2017-04-18 10:18:50

    Python 크롤러에 대해 잘 모르지만 일반적으로 Scrapy는 뒤틀린 비동기 프레임워크를 기반으로 사용됩니다.

    여러 프로세스는 다중 코어를 최대한 활용할 수 있습니다. 현재 이상적인 것은 다중 프로세스 + 코루틴입니다.

    요청에는 여전히 동기 방식이 사용되기 때문에 스레드를 차단하게 됩니다. 이 경우에는 asyncio.sleep 메서드 대신 time.sleep 메서드를 사용하는 것으로 이해할 수 있습니다. 비동기.

    회신하다
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-18 10:18:50

    이 기사를 확인하세요: http://aosabook.org/en/500L/a...

    회신하다
    0
  • PHP中文网

    PHP中文网2017-04-18 10:18:50

    asyncio은 하나의 스레드에서 여러 비동기 작업을 처리하는 ​​코루틴 아이디어를 채택했습니다. 타이밍, 비동기 IO 등 비동기 작업에는 무엇이 있나요?

    그런데 작업이 비동기식을 지원하지 않으면 어떻게 되나요?

    예를 들어 차단 IO를 읽고 쓰거나 시간이 많이 걸리는 많은 양의 계산을 수행합니다. 코루틴은 작업 차단 문제를 해결하고 멀티 프로세스와 멀티 스레드의 장점을 반영합니다.

    둘의 사용 시나리오는 다릅니다. 다양한 시나리오, 다양한 계획.

    회신하다
    0
  • PHP中文网

    PHP中文网2017-04-18 10:18:50

    asyncio에는 관련 타사 라이브러리 지원이 필요하므로 기본적으로 요청 및 http를 포함한 직렬 포트, 네트워크 프로토콜 등 모든 타사 라이브러리를 별도로 작성해야 하지만 좋은 경우에는 이후에 수행됩니다. 두 버전에서 사용된 많은 라이브러리는 비동기식이었습니다. 요청이 포함됩니다.

    회신하다
    0
  • PHPz

    PHPz2017-04-18 10:18:50

    asyncio를 지원하려면 비동기 API가 필요합니다(동기 비차단 API도 사용 가능하지만 Python에는 그런 기능이 없습니다 setInterval. 해킹해야 할 수도 있습니다).

    동기식 차단 API의 경우 하나의 콜백이 중단되면 다른 콜백을 실행할 수 없습니다. 지금까지 본 IO API는 기본적으로 차단되어 있습니다.

    회신하다
    0
  • 黄舟

    黄舟2017-04-18 10:18:50

    Python 멀티스레딩은 GIL의 존재로 인해 실용적이지 않지만, 멀티프로세스는 여전히 매우 유용합니다

    회신하다
    0
  • 취소회신하다