Linux 开发,使用多线程还是用 IO 复用 select/epoll?
每分钟有2K用户访问,服务器端处理请求选择用多线程(每个用户一个线程),还是用I/O复用?
5 条评论
默认排序
按时间排序
29 个回答
我是来指出排名第一的回答的BUG的:
"
多线程的模式有很多的,leader-follow还有Half-Sync/Half-Async.
现在这个年代,以我见过的代码,真没见过几个是一个连接上来就一个线程的了.
"
- 多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。多线程模型默认情况下,(在Linux)每个线程会开8M的栈空间,再TCP长连接的情况下,2000/分钟的请求,几乎可以假定有上万甚至十几万的并发连接,假定有10000个连接,开这么多个线程需要10000*8M=80G的内存空间!即使调整每个线程的栈空间,也很难满足更多的需求。甚至攻击者可以利用这一点发动DDoS,只要一个连接连上服务器什么也不做,就能吃掉服务器几M的内存,这不同于多进程模型,线程间内存无法共享,因为所有线程处在同一个地址空间中。内存是多线程模型的软肋。
- "
多线程的模式有很多的,leader-follow还有Half-Sync/Half-Async.
现在这个年代,以我见过的代码,真没见过几个是一个连接上来就一个线程的了.
每
分钟有2K?差点看成每秒钟。
这2K个用户是长连接呢还是短连接呢?如果是短连接,或者连接后通信不频繁的话,线程(池)就可以,如果一直是2k的并发,那还是异步(感谢 @程ocean提醒,补充完整:用epoll配合非阻塞IO实现,epoll本身并非异步)靠谱点。再高就要异步加并行(多线程/多进程)了。
这2K个用户是长连接呢还是短连接呢?如果是短连接,或者连接后通信不频繁的话,线程(池)就可以,如果一直是2k的并发,那还是异步(感谢 @程ocean提醒,补充完整:用epoll配合非阻塞IO实现,epoll本身并非异步)靠谱点。再高就要异步加并行(多线程/多进程)了。
我也同意event loop + thread pool的做法,
epoll + 多线程 + 多进程部署 效率真的不错。
先用select接口(poll/epoll,kq,iocp)接受请求,这样可以保证并发,在这个环节他只管收,不处理业务,把FD放到一个buffer(一个q里面),然后业务处理模型对接线程池。可以使复杂业务处理上的负担被分担。select+线程池,这样兼顾了并发(牺牲了一点性能),又保证了因为逻辑代码的简洁性。如果选择完全异步的方式,你就要在业务处理里面使用完全的异步API,至少很多数据库驱动,缓存驱动等等你需要用的到技术都没有提供异步API,很多业务要保障流程的正确是需要同步操作的,而且业务如果全部使用异步API,各种不明确回调和闭包导致内存暴栈的危险上升(我想各位应该被nodejs折磨过吧),对开发人员思考方式和技术实力都有较高的要求。一个部门里面有两个了解epoll就算技术非常NB的核心部门了吧,假若有能正确驾驭epoll,了解各种触发方式,状态机,特别是要能正确读写完整的信息,而没有造成大量的CLOSE_WAIT,是特别特别不易的。
我曾在tornado上面搭建过一个线程池。原型参见: nikoloss/iceworld · GitHub
虽然不算最完美的解决方案,但是也在工作中省去了很多烦恼。他的效率虽没有原生tornado高,但是非常适合多人合作(尽管如此效率还是要暴webpy几条街)。
epoll + 多线程 + 多进程部署 效率真的不错。
先用select接口(poll/epoll,kq,iocp)接受请求,这样可以保证并发,在这个环节他只管收,不处理业务,把FD放到一个buffer(一个q里面),然后业务处理模型对接线程池。可以使复杂业务处理上的负担被分担。select+线程池,这样兼顾了并发(牺牲了一点性能),又保证了因为逻辑代码的简洁性。如果选择完全异步的方式,你就要在业务处理里面使用完全的异步API,至少很多数据库驱动,缓存驱动等等你需要用的到技术都没有提供异步API,很多业务要保障流程的正确是需要同步操作的,而且业务如果全部使用异步API,各种不明确回调和闭包导致内存暴栈的危险上升(我想各位应该被nodejs折磨过吧),对开发人员思考方式和技术实力都有较高的要求。一个部门里面有两个了解epoll就算技术非常NB的核心部门了吧,假若有能正确驾驭epoll,了解各种触发方式,状态机,特别是要能正确读写完整的信息,而没有造成大量的CLOSE_WAIT,是特别特别不易的。
我曾在tornado上面搭建过一个线程池。原型参见: nikoloss/iceworld · GitHub
虽然不算最完美的解决方案,但是也在工作中省去了很多烦恼。他的效率虽没有原生tornado高,但是非常适合多人合作(尽管如此效率还是要暴webpy几条街)。
- 多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。多线程模型默认情况下,(在Linux)每个线程会开8M的栈空间,再TCP长连接的情况下,2000/分钟的请求,几乎可以假定有上万甚至十几万的并发连接,假定有10000个连接,开这么多个线程需要10000*8M=80G的内存空间!即使调整每个线程的栈空间,也很难满足更多的需求。甚至攻击者可以利用这一点发动DDoS,只要一个连接连上服务器什么也不做,就能吃掉服务器几M的内存,这不同于多进程模型,线程间内存无法共享,因为所有线程处在同一个地址空间中。内存是多线程模型的软肋。
- 在UNIX平台下多进程模型擅长处理并发长连接,但却不适用于连接频繁产生和关闭的情形。Windows平台忽略此项。 同样的连接需要的内存数量并不比多线程模型少,但是得益于操作系统虚拟内存的Copy on Write机制,fork产生的进程和父进程共享了很大一部分物理内存。但是多进程模型在执行效率上太低,接受一个连接需要几百个时钟周期,产生一个进程 可能消耗几万个CPU时钟周期,两者的开销不成比例。而且由于每个进程的地址空间是独立的,如果需要进行进程间通信的话,只能使用IPC进行进程间通 信,而不能直接对内存进行访问。在CPU能力不足的情况下同样容易遭受DDos,攻击者只需要连上服务器,然后立刻关闭连接,服务端则需要打开一个进程再关闭。
- 同时需要保持很多的长连接,而且连接的开关很频繁,最高效的模型是非阻塞、异步IO模型。而且不要用select/poll,这两个API的有着O(N)的时间复杂度。在Linux用epoll,BSD用kqueue,Windows用IOCP,或者用libevent封装的统一接口(对于不同平台libevent实现时采用各个平台特有的API),这些平台特有的API时间复杂度为O(1)。 然而在非阻塞,异步I/O模型下的编程是非常痛苦的。由于I/O操作不再阻塞,报文的解析需要小心翼翼,并且需要亲自管理维护每个链接的状态。并且为了充分利用CPU,还应结合线程池,避免在轮询线程中处理业务逻辑。
但这种模型的效率是极高的。以知名的http服务器nginx为例,可以轻松应付上千万的空连接+少量活动链接,每个连接连接仅需要几K的内核缓冲区,想要应付更多的空连接,只需简单的增加内存(数据来源为淘宝一位工程师的一次技术讲座,并未实测)。这使得DDoS攻击者的成本大大增加,这种模型攻击者只能将服务器的带宽全部占用,才能达到目的,而两方的投入是不成比例的。
有几种网络服务器模型分析如下
1. 一个连接一个线程模型:适用场景,连接少,且逻辑复杂。例如mysql采用此模型,一个连接一个线程。模型的一些小变体是线程采用线程池,避免创建销毁线程的开销
2. 半同步半异步模型:单独一个IO线程来异步处理网络IO,使用线程池来同步处理请求,业务逻辑的编写就会变得简单。适用于并发连接较多,但是每秒的请求量不太大的业务。可以参考
半同步半异步I/O的设计模式(half sync/half async)
也可以参考我的
handy/hsha.cc at master · yedf/handy · GitHub
Leader/Follower模式属于这个模型的变体
3. 全异步模型:网络IO和业务处理都是异步的,一个线程可以处理所有的任务,程序不会阻塞在任何一个网络IO或者磁盘IO上。这种模型能够最大程度利用计算机的性能,但是全异步的处理让业务的编写变得非常复杂。nginx这是这种模型,他是多进程,每个worker都是一样的,没有不同。memcache也是类似的,它的多线程完全是为了利用多个cpu能力,单线程也能够完整的跑整个业务的。
楼主的每分钟2k用户,按照通常的访问情况,每秒也就4,5个请求,最适合应该是半同步半异步模型。
----------------------------------------------
2017.1.22更新
全异步模型是性能最好,同时也是开发难度最高的模型。为了追求更好的性能,许多语言例如C++,C#,GO,nodejs,python都尝试简化此模型的编程,推出了支持异步编程的语言特性。
C++的future,promise
C#的async/await
GO的goroutine
nodejs的Promise=>generator=>async/await
python的yield
在这些特性的支持下,全异步编程可以做到与同步编程非常接近,可读性良好。其中GoLang是近年来新推出的语言,专门为高并发设计了goroutine,对此模型的支持最佳,使用体验也最好。
1. 一个连接一个线程模型:适用场景,连接少,且逻辑复杂。例如mysql采用此模型,一个连接一个线程。模型的一些小变体是线程采用线程池,避免创建销毁线程的开销
2. 半同步半异步模型:单独一个IO线程来异步处理网络IO,使用线程池来同步处理请求,业务逻辑的编写就会变得简单。适用于并发连接较多,但是每秒的请求量不太大的业务。可以参考
半同步半异步I/O的设计模式(half sync/half async)
也可以参考我的
handy/hsha.cc at master · yedf/handy · GitHub
Leader/Follower模式属于这个模型的变体
3. 全异步模型:网络IO和业务处理都是异步的,一个线程可以处理所有的任务,程序不会阻塞在任何一个网络IO或者磁盘IO上。这种模型能够最大程度利用计算机的性能,但是全异步的处理让业务的编写变得非常复杂。nginx这是这种模型,他是多进程,每个worker都是一样的,没有不同。memcache也是类似的,它的多线程完全是为了利用多个cpu能力,单线程也能够完整的跑整个业务的。
楼主的每分钟2k用户,按照通常的访问情况,每秒也就4,5个请求,最适合应该是半同步半异步模型。
----------------------------------------------
2017.1.22更新
全异步模型是性能最好,同时也是开发难度最高的模型。为了追求更好的性能,许多语言例如C++,C#,GO,nodejs,python都尝试简化此模型的编程,推出了支持异步编程的语言特性。
C++的future,promise
C#的async/await
GO的goroutine
nodejs的Promise=>generator=>async/await
python的yield
在这些特性的支持下,全异步编程可以做到与同步编程非常接近,可读性良好。其中GoLang是近年来新推出的语言,专门为高并发设计了goroutine,对此模型的支持最佳,使用体验也最好。
虽然蓝形参(排名第一的那位)说得不好。但是,我觉得也没有误人子弟那么严重。至少有很多地方还是说得不错得。这里我赞一个。
我纠正一下蓝大哥的第一点。如果只有多线程而没有其他的机制的话,那系统确实只能一个connection对应一个线程(非常危险的做法。线程挂了,你系统就over了)。
我也非常同意:event loop + thread pool 的做法。
我纠正一下蓝大哥的第一点。如果只有多线程而没有其他的机制的话,那系统确实只能一个connection对应一个线程(非常危险的做法。线程挂了,你系统就over了)。
我也非常同意:event loop + thread pool 的做法。
枫亦 程序员
这个问题我认为还是得看需求吧,如果要寻求通用简单的方式用语义更加丰富的语言比如go或许更合适一些。对于短连接和长连接,有些场合是这样的:刚刚接入的前几次通信可能是短连接,之后可能转入长连接、或者断开、或者一直维持短连接。对于这样的需求,那么对刚刚接入的连接我认为没必要单独拉一个工作线程,我喜欢把这些连接放在一个公共的工作线程中处理,维护一组连接状态机。如果达到触发长连接的条件,则单独拉一个工作线程进行处理。IO机制方面,公共的工作线程中需要同时监听多个套接字,我一般采用select/poll或者epoll,而转入长连接的工作线程我直接用阻塞同步方式,当然肯定需要弄个阻塞时间上限。目前看我的这种解决方式对于我们的小需求表现还能接受,合适就好啦。
Cyandev 大二学生,资深果粉,编程初学者
epoll 多路复用肯定是要用的了,至于 accept 以后怎么处理客户端连接,这个可以衡量一下直接用异步 IO 还是用线程池处理连接。
每个用户一个线程太可怕了……
每个用户一个线程太可怕了……
匿名用户
这个问题真的是老生常谈了,初期可以把几个方案都做做,能学到不少东西。但是一旦你熟练掌握了,做得太多跟月经似的周期性时,我非常认真的建议你用 Erlang,实在不行用 Go 也不错,再不行如果处理的都是些 Web 方面的小业务而且对容错性的要求不是很高,那用 node.js 也比整天挖 C/C++ 里的 epool/kqueue/IOCP 那点老坟有趣多了。
网络 IO 模型就那么几种,尽管套着所谓高性能的大帽子,但不管哪种其实用起来都很蹩脚。因为根源在于 C/C++ 对并发的支持实在是可怜到发指。只有库级别的支持,没有任何语义层面的机制。
C/C++ 在服务端高并发IO密集型业务方面生产效率和可维护性方面低得令人发指,完全抵消掉了那一点点性能方面的增益。
最后再强调一遍,学习一下各种网络模型有百利无一害,但也仅此而已,在生产环境中你应该用一些更可靠的实现,这方面的轮子真的很无聊。
网络 IO 模型就那么几种,尽管套着所谓高性能的大帽子,但不管哪种其实用起来都很蹩脚。因为根源在于 C/C++ 对并发的支持实在是可怜到发指。只有库级别的支持,没有任何语义层面的机制。
C/C++ 在服务端高并发IO密集型业务方面生产效率和可维护性方面低得令人发指,完全抵消掉了那一点点性能方面的增益。
最后再强调一遍,学习一下各种网络模型有百利无一害,但也仅此而已,在生产环境中你应该用一些更可靠的实现,这方面的轮子真的很无聊。
知乎用户 libconcurrency,http://github.com/zhouzhenghui
很多人关注这个问题,我相信焦点不在于每分钟多少k用户上,而是这两种方式带来在编程结构上的差异。
不谈多线程在性能上的优劣势,在单核时代就已经被普遍应用的这个技术主要带来的变化是逻辑隔离。代码的复杂性相对被简化了,也减少了局部不断重构的需求。
如今多核时代下,我们还需要多线程压榨出足够的性能,同时避免过多线程带来的调度开销。结合上述编码的需求,需要在某些方面作出根本的变革。
Erlang和go走在正确的路线上,但还是不够或存有技术上的瑕疵。
不谈多线程在性能上的优劣势,在单核时代就已经被普遍应用的这个技术主要带来的变化是逻辑隔离。代码的复杂性相对被简化了,也减少了局部不断重构的需求。
如今多核时代下,我们还需要多线程压榨出足够的性能,同时避免过多线程带来的调度开销。结合上述编码的需求,需要在某些方面作出根本的变革。
Erlang和go走在正确的路线上,但还是不够或存有技术上的瑕疵。