推荐学习书目
› Learn Python the Hard Way
Python Sites
› PyPI - Python Package Index
› http://diveintopython.org/toc/index.html
› Pocoo
值得关注的项目
› PyPy
› Celery
› Jinja2
› Read the Docs
› gevent
› pyenv
› virtualenv
› Stackless Python
› Beautiful Soup
› 结巴中文分词
› Green Unicorn
› Sentry
› Shovel
› Pyflakes
› pytest
Python 编程
› pep8 Checker
Styles
› PEP 8
› Google Python Style Guide
› Code Style from The Hitchhiker's Guide
Te11UA
V2EX  ›  Python

多线程与协程爬虫有很大区别吗?多线程转协程能优化多少?

  •  1
     
  •   Te11UA · Aug 6, 2020 · 9550 views
    This topic created in 2281 days ago, the information mentioned may be changed or developed.
    目前一个爬虫项目运行在 4c8g 下的机器,requests 运行在 4 进程* 400 线程下,单页面 200k 左右。
    最近发现 CPU 和内存都有一点瓶颈,如果转成协程的话是否能优化爬取速率呢?
    (不讨论反爬的情况,单纯从爬虫效率上看)
    54 replies  •  2020-08-08 19:03:25 +08:00
    xiaolinjia
    xiaolinjia
    Aug 6, 2020
    1
    理论上协程少了线程之间的切换,速度和内存应该会更优点。
    araraloren
    araraloren
    Aug 6, 2020
    2
    协程的东西都运行在一个线程,python 的话应该有不少优势,毕竟 python 有 GIL
    Vegetable
    Vegetable
    Aug 6, 2020
    3
    协程理论上是更好的方案,但是效率上是否能提高存疑。
    你这已经有 1600 线程了,单页面 200k 就是 200k*1600 同时下载,超过 300mb 的流量,带宽不会成为瓶颈吗
    locoz
    locoz
    Aug 6, 2020
    4
    对于爬虫而言,从多线程换成协程在性能方面提升很小,甚至如果你操作流程中有同步的东西的话还会降低性能。但用协程来做之后,内存占用方面会有明显下降,也就是说你可能能开更高的并发,相对而言也会有一定的采集速率提升。
    youxiachai
    youxiachai
    Aug 6, 2020
    5
    @locoz ls 有个大佬做了一个算术题, 相比更高的并发...带宽才是瓶颈吧..
    wysnylc
    wysnylc
    Aug 6, 2020
    6
    协程是跑在一个线程里,多线程转成协程除了内存占用下降性能也会下降
    协程就不是这么用的别无脑协程
    locoz
    locoz
    Aug 6, 2020
    7
    @youxiachai #5 带宽是可以随便加的,而且也不会有真正意义上的同时下载(毕竟服务器返回的时间并不相同),所以其实带宽并不是什么大问题...
    locoz
    locoz
    Aug 6, 2020
    8
    @wysnylc #6 你忘了 GIL
    Te11UA
    Te11UA
    OP
    Aug 6, 2020
    9
    @Vegetable 带宽实际上没有那么多,因为代理质量一般,而且有一定的解析操作。用的是按量付费,看监控就十几 MB 而已。
    Chichele
    Chichele
    Aug 6, 2020 via Android
    10
    gevent 使用很简单,可以直接试试
    Te11UA
    Te11UA
    OP
    Aug 6, 2020
    11
    @locoz 了解,我去尝试先,目前内存的确是有点瓶颈
    newmlp
    newmlp
    Aug 6, 2020❤️5
    12
    别骗自己了,就线程和协程那点性能差异你用一行垃圾代码就搞没了,还是花时间优化下自己的业务代码吧
    hakono
    hakono
    Aug 6, 2020 via Android
    13
    400 个线程,你 cpu 不瓶颈就怪了。。。。。
    现代的程序一般是一个线程对应 cpu 的一个核心性能才比较好,开 400 个线程和以前单核 cou 跑多线程一样性能没什么提升反倒降低性能

    这个情况用协程应该能改善楼主的 cpu 和内存使用情况,而且协程可以实现比多线程更高的并发(协程可以一次轻松发出几千几万个请求,你用线程难道开几千个几万个线程么)
    当然你网络带宽死的话,协程也只是比线程更省资源罢了
    wysnylc
    wysnylc
    Aug 6, 2020
    14
    @newmlp #12 "协程可以一次轻松发出几千几万个请求,你用线程难道开几千个几万个线程么"
    看的我尬的不行
    xcstream
    xcstream
    Aug 6, 2020
    15
    没多大用 又不是服务器
    itskingname
    itskingname
    Aug 6, 2020 via iPhone
    16
    之前爬斗鱼直播弹幕,第一版爬虫用的多线程,5 台服务器最多能同时监控 250 个房间的弹幕。后来使用 asyncio 重构成异步爬虫,只用 3 台机器,可以同时监控 25000 个房间。
    zengxs
    zengxs
    Aug 6, 2020
    17
    曾经和小伙伴讨论过这个问题

    异步编程要考虑不能引入同步的代码,而 Python 的生态目前来讲,大部分库仍然是同步的,因此强行上异步,会造成开发效率的下降,而性能却不见得能比用多线程快很多

    我们最后得出的结论是,除非有一个场景,特别适合使用 asyncio,否则一般还是倾向于使用多线程
    zengxs
    zengxs
    Aug 6, 2020
    18
    看了一下楼主的描述,4 核却开了 400 线程,cpu 做线程调度的开销都很大了吧

    不考虑反爬单纯考虑性能的话,我觉得可以考虑换成协程
    supermoonie
    supermoonie
    Aug 6, 2020 via iPhone
    19
    还不如异步 io
    saberlong
    saberlong
    Aug 6, 2020 via Android
    20
    @wysnylc 现在一般普通主流计算机,直接开几万协程确实没问题。以前开发写测试时,不小心开了几十万协程,还能跑。但是线程却不行,资源占用和上下文切换决定了它不可能开启同样多的数量时还能保证可用。
    你的怀疑点应该是一次性发出这么多请求。这得看理解了,如果指发出第一个数据包开始,确实可能。
    假设极端情况,被访问的服务器响应很慢,导致发出数据包请求后就在 io 等待。那么协程确实能做到同时有几万个请求在等待响应。只是线程要做到同样量级,需要机器资源就更多了。
    不过实际上并没有优势,现实中不会这么极端,瓶颈通常也不在这。
    auxox
    auxox
    Aug 6, 2020
    21
    系统优化的第一步是先找到瓶颈点
    saberlong
    saberlong
    Aug 6, 2020 via Android
    22
    @wysnylc 另外你可以找下百万 Go TCP, 百万 websocket 同时连接的文章。有每个连接使用 go 程,和 reactor epoll 的性能对比。
    newmlp
    newmlp
    Aug 6, 2020
    23
    @wysnylc 你的认知就觉得除了协程,就没其他办法一次发出几千几万个请求了?
    Vibra
    Vibra
    Aug 6, 2020 via iPhone
    24
    省了切换的时间,但是尽管有 Gil 的存在,因为线程 io 的自动切换,其实差不了太多。
    wysnylc
    wysnylc
    Aug 6, 2020
    25
    @newmlp #23 你是不是回错人了
    wysnylc
    wysnylc
    Aug 6, 2020
    26
    @saberlong #22 你是不是忘了 netty 是用什么写的?别无脑吹 go 了百万链接瓶颈根本不在线程协程而是内存带宽磁盘,线程创建也没那么耗费资源
    给你个链接学学吧:
    为什么 Java 坚持多线程不选择协程? - 大宽宽的回答 - 知乎
    https://www.zhihu.com/question/332042250/answer/734115120
    ClericPy
    ClericPy
    Aug 6, 2020❤️1
    27
    Talk is cheap, 试试就知道了

    直接说结论:

    1. 本次测试不记带宽发送本地端口的请求, 本地 server 使用 golang 默认的 net/http 简单实现的

    2. 对比 httpx 协程和 requests 多线程, 二者都使用连接池复用连接. 前者比后者快了大概 20%(虽然后者被我负优化了...), 启用 uvloop 以后也没拉开太大差距.

    3. 对比 aiohttp 和 requests, 不使用 uvloop 的时候前者是后者 3 倍, 开了 uvloop 大概 3068 qps, 是后者的 3.15 倍, 而 golang 内置的 net/http 测试也才 3300. 虽然很大因素是 aiohttp 内核很多地方使用 Cython 实现的...

    4. 对比 windows vs linux, 前者是游戏本所以多核(但是只能用一个...), 后者在阿里云服务器上虽然单 CPU 但是有 uvloop 加成, 速度提高一倍

    部分代码: https://github.com/ClericPy/torequests#benchmarks


    一句话总结, 以上测试纯属娱乐不作数, 真想用快的, 就选 aiohttp 就好了

    之前貌似还看到, 裸 uvloop 单核情况下和 golang 差不多
    silencefly
    silencefly
    Aug 6, 2020 via iPhone
    28
    多进程加协程
    OysterQAQ
    OysterQAQ
    Aug 6, 2020
    29
    协程属于用户空间的多线程实现,有上下文切换 但是会比内核空间的上下文切换代价小很多,但是如果从瓶颈来看的话 需要看看要爬的网站是否有连接数以及速率的限制,楼上说 cpu 只有 4 核多线程没意义,但是其实爬虫这种 io 密集型场景,线程在 http 请求阻塞的时候是不占用 cpu 的,超过核心数的线程在这时候能更好的占满 cpu,不至于让 cpu 空转
    wuhanchu
    wuhanchu
    Aug 6, 2020 via iPhone
    30
    不应该一起用吗
    chihiro2014
    chihiro2014
    Aug 6, 2020
    31
    协程不就是单线程线程池么
    WaldenHorizon
    WaldenHorizon
    Aug 7, 2020
    32
    如果更换协程后仍然限制 1600 总并发,猜测 cpu 稍微减轻,内存较大下降;不限制并发,cpu 和带宽会有一个顶满
    binux
    binux
    Aug 7, 2020
    33
    @wysnylc 我发现你根本就没看懂这里讨论的问题是什么。
    你用 netty 搞 IO 异步 + worker 线程和 python 的协程没有任何区别。
    wnpllrzodiac
    wnpllrzodiac
    Aug 7, 2020 via Android
    34
    协程不能多核,自己开多实例。但是代码逻辑简单清晰易维护
    xiaolinjia
    xiaolinjia
    Aug 7, 2020
    35
    @wnpllrzodiac 之前看过一个库,说是可以多进程+协程。叫 aiomultiprocess 。
    cwjokaka
    cwjokaka
    Aug 7, 2020
    36
    转成协程对应使用的库也要改成异步的,不小心用了阻塞的方法就完蛋
    daiqiangbudainiu
    daiqiangbudainiu
    Aug 7, 2020
    37
    看上边说 GIL 的,你们真的理解 GIL 是啥吗?他这个又不是 CPU 负载高,GIL 没啥影响
    CriseLYJ
    CriseLYJ
    Aug 7, 2020
    38
    多进程+异步实现,充分利用多个 cpu,异步减少 cpu 资源消耗。
    CODEWEA
    CODEWEA
    Aug 7, 2020
    39
    你资源就那么大,再玩也没有招
    knva
    knva
    Aug 7, 2020
    40
    没啥区别,爬虫爬快了不怕对面 block ?
    chazyu1996
    chazyu1996
    Aug 7, 2020
    41
    没有 sleep 的爬虫没有灵魂,那么快有啥用
    est
    est
    Aug 7, 2020
    42
    @chazyu1996 真相了。
    keenkiller
    keenkiller
    Aug 7, 2020
    43
    你如果是 4 核的话,400 个线程有点过分啊…虽然 IO 密集型,也不能这么夸张吧
    binux
    binux
    Aug 7, 2020 via Android
    44
    Cloutain
    Cloutain
    Aug 7, 2020
    45
    在多线程下再实现协程,这样避免过多的用户态内核态切换。
    hakono
    hakono
    Aug 7, 2020
    46
    @wysnylc #14 如果你有什么高见还请你说清楚。不过我反倒觉得你在这楼的回复让人看得尬死了。
    skinny
    skinny
    Aug 7, 2020
    47
    瓶颈难道不是在反爬、被爬网络带宽和服务质量、爬虫网络带宽吗?
    老说协程提高效率节省资源什么的,一些时候确实是这样,但是在一般爬虫项目里线程协程真的没什么差别,我同意 12 楼的看法。
    wysnylc
    wysnylc
    Aug 7, 2020
    48
    @hakono #46 "现在一般普通主流计算机,直接开几万协程确实没问题。以前开发写测试时,不小心开了几十万协程,还能跑。但是线程却不行,资源占用和上下文切换决定了它不可能开启同样多的数量时还能保证可用。"
    协程也是跑在线程之上,如果真的同时运行几十万线程而不挂只有一种可能,活跃的线程其实没几个,我后面贴的为什么 Java 坚持多线程不选择协程?里面有这样一句话:上面的讨论简化了 RSS 和 VM 的区别。实际上一个线程启动后只会在虚拟地址上占位置那么多的内存。除非实际用上,是不会真的消耗物理内存的。
    所以所谓的"几万"根本就是扯淡,多协程本质上和多线程没有区别,多线程该有的问题多协程一样有,而单个协程是跑在单线程上单线程可能跑几万请求?用脚指头想也知道答案

    "另外你可以找下百万 Go TCP, 百万 websocket 同时连接的文章。有每个连接使用 go 程,和 reactor epoll 的性能对比。
    "
    老 Go 吹了,上手就是百万 233333 教程一大把就是没啥公司用
    cocowind
    cocowind
    Aug 7, 2020
    49
    python 同一个业务内全使用 aio 家族
    bytesmith
    bytesmith
    Aug 7, 2020
    50
    别想啦,你这估计差不多到极限了,你费劲巴拉搞的协程,还不如直接加硬件来的实在。
    bytesmith
    bytesmith
    Aug 7, 2020
    51
    或者用 工具看看 cpu 的瓶颈在哪里, 是都用在切换上下文了还是说在跑 计算密集的任务,然后在决定怎么优化,别无脑优化
    sunriz
    sunriz
    Aug 8, 2020
    52
    爬虫应该是 Io 密集的吧
    black11black
    black11black
    Aug 8, 2020
    53
    楼上一大堆人不知道说的啥。IO 复用不光是解决内核态开销问题,还有一个大问题是解决线程切片时间的问题啊。

    你以为的多线程+GIL:一个线程执行完了释放 GIL,切换到下一个线程,申请 GIL,开始业务逻辑
    实际的多线程+GIL:一个线程执行完了释放 GIL,等待很长时间,系统 call 你了才能切换
    ClericPy
    ClericPy
    Aug 8, 2020❤️1
    54
    看到有几个回复挺反常识的, 提醒几个 Python 并发编程的常识问题吧

    1. 线程开的越多, 执行起来就越快吗?

    并不会.

    一方面, 线程开太多, CPU 切换的成本会变高, 也就相对降低了 CPU 利用率, CPU 很多时间浪费在调度上而不是计算上. 有关怎么切换的, 可以随处找找 GIL 的文章, 不过还是不建议自己修改对应参数 setswitchinterval (旧版本的 setcheckinterval )
    另一方面, 对爬虫来说, 如果连接速度靠谱的话, 有可能一个线程就跑满了带宽, 那开多线程除了让所有任务一起抢资源, 并不会降低总时长, 也就是常见场景: 为什么我开 5 线程比开 100 线程还快(或者差不多). 与普通程序不同, 爬虫程序传输数据一方面看你的带宽, 另一方面还特别看重目标服务器的负载能力.

    2. 有一个比较合理的并发数量吗?

    参考:
    Python3 里面 ThreadPoolExecutor 的 max_workers 默认值是 (os.cpu_count() or 1) * 5
    可以根据带宽使用率适当调整这个数值.
    (另: 多进程 ProcessPoolExecutor 默认 max_workers 就是 os.cpu_count() or 1)


    3. 不计带宽和 CPU 能力的情况下, 是不是线程开的越多, 速度越快?

    也不完全是.

    拿 Requests 库来举例, 它的 Session 默认连接池大小取决于 HTTPAdapter 对象的 pool_connections, 这个默认值 DEFAULT_POOLSIZE = 10
    简单的说, 如果不修改 HTTPAdapter 连接池的大小, 那可能瓶颈基本限定在这里了. 至于有些人选择不用 Session 复用连接, 我举个例子算了: 之前抓某东的某数据, 复用连接的情况下速度比每次新建连接大概快了十几倍.

    4. 是不是用协程就比线程快, 节省 CPU?

    不一定.

    协程提高的是 CPU 效率, 遇到高并发的抓取, 你会发现协程 CPU 一直 100%, 因为它真的很忙, 而多线程反而可能在 80~100% 波动. 至于速度, 简单的提个例子, falcon 那是相当擅长 Benchmark.

    协程中最使我受益的并不是性能, 而是它属于随时可以 Cancel 的, 一个已经执行的线程, 想在外部杀死它简直太费劲了.
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2388 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 86ms · UTC 15:34 · PVG 23:34 · LAX 07:34 · JFK 10:34
    ♥ Do have faith in what you're doing.