1
xiaolinjia 2020-08-06 15:47:32 +08:00
理论上协程少了线程之间的切换,速度和内存应该会更优点。
|
2
araraloren 2020-08-06 15:53:22 +08:00
协程的东西都运行在一个线程,python 的话应该有不少优势,毕竟 python 有 GIL
|
3
Vegetable 2020-08-06 16:12:12 +08:00
协程理论上是更好的方案,但是效率上是否能提高存疑。
你这已经有 1600 线程了,单页面 200k 就是 200k*1600 同时下载,超过 300mb 的流量,带宽不会成为瓶颈吗 |
4
locoz 2020-08-06 16:17:53 +08:00
对于爬虫而言,从多线程换成协程在性能方面提升很小,甚至如果你操作流程中有同步的东西的话还会降低性能。但用协程来做之后,内存占用方面会有明显下降,也就是说你可能能开更高的并发,相对而言也会有一定的采集速率提升。
|
5
youxiachai 2020-08-06 16:20:45 +08:00
@locoz ls 有个大佬做了一个算术题, 相比更高的并发...带宽才是瓶颈吧..
|
6
wysnylc 2020-08-06 16:30:19 +08:00
协程是跑在一个线程里,多线程转成协程除了内存占用下降性能也会下降
协程就不是这么用的别无脑协程 |
7
locoz 2020-08-06 16:38:45 +08:00
@youxiachai #5 带宽是可以随便加的,而且也不会有真正意义上的同时下载(毕竟服务器返回的时间并不相同),所以其实带宽并不是什么大问题...
|
9
Te11UA OP @Vegetable 带宽实际上没有那么多,因为代理质量一般,而且有一定的解析操作。用的是按量付费,看监控就十几 MB 而已。
|
10
tigerstudent 2020-08-06 17:14:59 +08:00 via Android
gevent 使用很简单,可以直接试试
|
12
newmlp 2020-08-06 17:30:55 +08:00 5
别骗自己了,就线程和协程那点性能差异你用一行垃圾代码就搞没了,还是花时间优化下自己的业务代码吧
|
13
hakono 2020-08-06 17:50:33 +08:00 via Android
400 个线程,你 cpu 不瓶颈就怪了。。。。。
现代的程序一般是一个线程对应 cpu 的一个核心性能才比较好,开 400 个线程和以前单核 cou 跑多线程一样性能没什么提升反倒降低性能 这个情况用协程应该能改善楼主的 cpu 和内存使用情况,而且协程可以实现比多线程更高的并发(协程可以一次轻松发出几千几万个请求,你用线程难道开几千个几万个线程么) 当然你网络带宽死的话,协程也只是比线程更省资源罢了 |
15
xcstream 2020-08-06 17:54:04 +08:00
没多大用 又不是服务器
|
16
itskingname 2020-08-06 18:22:22 +08:00 via iPhone
之前爬斗鱼直播弹幕,第一版爬虫用的多线程,5 台服务器最多能同时监控 250 个房间的弹幕。后来使用 asyncio 重构成异步爬虫,只用 3 台机器,可以同时监控 25000 个房间。
|
17
zengxs 2020-08-06 18:31:10 +08:00
曾经和小伙伴讨论过这个问题
异步编程要考虑不能引入同步的代码,而 Python 的生态目前来讲,大部分库仍然是同步的,因此强行上异步,会造成开发效率的下降,而性能却不见得能比用多线程快很多 我们最后得出的结论是,除非有一个场景,特别适合使用 asyncio,否则一般还是倾向于使用多线程 |
18
zengxs 2020-08-06 18:37:37 +08:00
看了一下楼主的描述,4 核却开了 400 线程,cpu 做线程调度的开销都很大了吧
不考虑反爬单纯考虑性能的话,我觉得可以考虑换成协程 |
19
supermoonie 2020-08-06 18:42:54 +08:00 via iPhone
还不如异步 io
|
20
saberlong 2020-08-06 19:01:50 +08:00 via Android
@wysnylc 现在一般普通主流计算机,直接开几万协程确实没问题。以前开发写测试时,不小心开了几十万协程,还能跑。但是线程却不行,资源占用和上下文切换决定了它不可能开启同样多的数量时还能保证可用。
你的怀疑点应该是一次性发出这么多请求。这得看理解了,如果指发出第一个数据包开始,确实可能。 假设极端情况,被访问的服务器响应很慢,导致发出数据包请求后就在 io 等待。那么协程确实能做到同时有几万个请求在等待响应。只是线程要做到同样量级,需要机器资源就更多了。 不过实际上并没有优势,现实中不会这么极端,瓶颈通常也不在这。 |
21
auxox 2020-08-06 19:10:19 +08:00
系统优化的第一步是先找到瓶颈点
|
22
saberlong 2020-08-06 19:30:24 +08:00 via Android
@wysnylc 另外你可以找下百万 Go TCP, 百万 websocket 同时连接的文章。有每个连接使用 go 程,和 reactor epoll 的性能对比。
|
24
Vibra 2020-08-06 19:54:35 +08:00 via iPhone
省了切换的时间,但是尽管有 Gil 的存在,因为线程 io 的自动切换,其实差不了太多。
|
26
wysnylc 2020-08-06 21:24:37 +08:00
@saberlong #22 你是不是忘了 netty 是用什么写的?别无脑吹 go 了百万链接瓶颈根本不在线程协程而是内存带宽磁盘,线程创建也没那么耗费资源
给你个链接学学吧: 为什么 Java 坚持多线程不选择协程? - 大宽宽的回答 - 知乎 https://www.zhihu.com/question/332042250/answer/734115120 |
27
ClericPy 2020-08-06 21:25:13 +08:00 1
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 差不多 |
28
silencefly 2020-08-06 21:31:11 +08:00 via iPhone
多进程加协程
|
29
OysterQAQ 2020-08-06 21:45:56 +08:00
协程属于用户空间的多线程实现,有上下文切换 但是会比内核空间的上下文切换代价小很多,但是如果从瓶颈来看的话 需要看看要爬的网站是否有连接数以及速率的限制,楼上说 cpu 只有 4 核多线程没意义,但是其实爬虫这种 io 密集型场景,线程在 http 请求阻塞的时候是不占用 cpu 的,超过核心数的线程在这时候能更好的占满 cpu,不至于让 cpu 空转
|
30
wuhanchu 2020-08-06 22:06:08 +08:00 via iPhone
不应该一起用吗
|
31
chihiro2014 2020-08-06 22:10:35 +08:00
协程不就是单线程线程池么
|
32
wangritian 2020-08-07 01:56:01 +08:00
如果更换协程后仍然限制 1600 总并发,猜测 cpu 稍微减轻,内存较大下降;不限制并发,cpu 和带宽会有一个顶满
|
33
binux 2020-08-07 02:09:09 +08:00
@wysnylc 我发现你根本就没看懂这里讨论的问题是什么。
你用 netty 搞 IO 异步 + worker 线程和 python 的协程没有任何区别。 |
34
wnpllrzodiac 2020-08-07 07:28:01 +08:00 via Android
协程不能多核,自己开多实例。但是代码逻辑简单清晰易维护
|
35
xiaolinjia 2020-08-07 08:50:25 +08:00
@wnpllrzodiac 之前看过一个库,说是可以多进程+协程。叫 aiomultiprocess 。
|
36
cwjokaka 2020-08-07 09:25:40 +08:00
转成协程对应使用的库也要改成异步的,不小心用了阻塞的方法就完蛋
|
37
warcraft1236 2020-08-07 09:27:03 +08:00
看上边说 GIL 的,你们真的理解 GIL 是啥吗?他这个又不是 CPU 负载高,GIL 没啥影响
|
38
CriseLYJ 2020-08-07 09:45:57 +08:00
多进程+异步实现,充分利用多个 cpu,异步减少 cpu 资源消耗。
|
39
CODEWEA 2020-08-07 10:25:55 +08:00
你资源就那么大,再玩也没有招
|
40
knva 2020-08-07 11:28:55 +08:00
没啥区别,爬虫爬快了不怕对面 block ?
|
41
chazyu1996 2020-08-07 11:36:14 +08:00
没有 sleep 的爬虫没有灵魂,那么快有啥用
|
42
est 2020-08-07 11:46:17 +08:00
@chazyu1996 真相了。
|
43
tairan2006 2020-08-07 13:25:39 +08:00
你如果是 4 核的话,400 个线程有点过分啊…虽然 IO 密集型,也不能这么夸张吧
|
44
binux 2020-08-07 13:29:43 +08:00 via Android
@chazyu1996 #41
|
45
Cloutain 2020-08-07 15:37:28 +08:00
在多线程下再实现协程,这样避免过多的用户态内核态切换。
|
47
skinny 2020-08-07 16:26:52 +08:00
瓶颈难道不是在反爬、被爬网络带宽和服务质量、爬虫网络带宽吗?
老说协程提高效率节省资源什么的,一些时候确实是这样,但是在一般爬虫项目里线程协程真的没什么差别,我同意 12 楼的看法。 |
48
wysnylc 2020-08-07 16:36:28 +08:00
@hakono #46 "现在一般普通主流计算机,直接开几万协程确实没问题。以前开发写测试时,不小心开了几十万协程,还能跑。但是线程却不行,资源占用和上下文切换决定了它不可能开启同样多的数量时还能保证可用。"
协程也是跑在线程之上,如果真的同时运行几十万线程而不挂只有一种可能,活跃的线程其实没几个,我后面贴的为什么 Java 坚持多线程不选择协程?里面有这样一句话:上面的讨论简化了 RSS 和 VM 的区别。实际上一个线程启动后只会在虚拟地址上占位置那么多的内存。除非实际用上,是不会真的消耗物理内存的。 所以所谓的"几万"根本就是扯淡,多协程本质上和多线程没有区别,多线程该有的问题多协程一样有,而单个协程是跑在单线程上单线程可能跑几万请求?用脚指头想也知道答案 "另外你可以找下百万 Go TCP, 百万 websocket 同时连接的文章。有每个连接使用 go 程,和 reactor epoll 的性能对比。 " 老 Go 吹了,上手就是百万 233333 教程一大把就是没啥公司用 |
49
sss495088732 2020-08-07 16:43:53 +08:00
python 同一个业务内全使用 aio 家族
|
50
bytesmith 2020-08-07 16:56:29 +08:00
别想啦,你这估计差不多到极限了,你费劲巴拉搞的协程,还不如直接加硬件来的实在。
|
51
bytesmith 2020-08-07 17:01:00 +08:00
或者用 工具看看 cpu 的瓶颈在哪里, 是都用在切换上下文了还是说在跑 计算密集的任务,然后在决定怎么优化,别无脑优化
|
52
sunriz 2020-08-08 00:57:57 +08:00
爬虫应该是 Io 密集的吧
|
53
black11black 2020-08-08 09:55:22 +08:00
楼上一大堆人不知道说的啥。IO 复用不光是解决内核态开销问题,还有一个大问题是解决线程切片时间的问题啊。
你以为的多线程+GIL:一个线程执行完了释放 GIL,切换到下一个线程,申请 GIL,开始业务逻辑 实际的多线程+GIL:一个线程执行完了释放 GIL,等待很长时间,系统 call 你了才能切换 |
54
ClericPy 2020-08-08 19:03:25 +08:00 1
看到有几个回复挺反常识的, 提醒几个 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 的, 一个已经执行的线程, 想在外部杀死它简直太费劲了. |