常见多线程与并发服务器设计方案举例

一、3点基础知识

1、一个主机的端口号为所有进程所共享,但普通用户进程绑定bind不了一些特殊端口号如20、80等。 

    多个进程不能同时监听listen同一个端口,会失败。当然父进程可以先listen然后fork多个子进程,多个子进程都可以accept这个sock,即抢夺式响应(惊群效应)。

    关注4元组是否能唯一确定一个连接?

2、每个进程都有自己的文件描述符(包括file fd, socket fd, timer fd, event fd, signal fd),一般是1024,可以通过ulimit -n 设置,但所有进程打开的文件描述符总数有上限,跟主机的内存有关。

3、一个进程内的所有线程共享进程的文件描述符。

二、常见并发服务器方案:

1、循环式/迭代式( iterative )服务器 无法充分利用多核CPU,不适合执行时间较长的服务,即适用于短连接。如果是长连接则需要在read/write之间循环,那么只能服务一个客户端。

2、并发式(concurrent)服务器 one connection per process/one connection per thread

适合执行时间比较长的服务

one connection per process : 主进程每次fork 之后要关闭connfd,子进程要关闭listenfd

one connection per thread : 主线程每次accept 回来就创建一个子线程服务,由于线程共享文件描述符,故不用关闭。

3、prefork or pre threaded(UNP2e 第27章)(容易发生“惊群”现象,即多个子进程都处于accept状态)

4、反应式( reactive )服务器 (reactor模式)(select/poll/epoll) 并发处理多个请求,实际上是在一个线程中完成。无法充分利用多核CPU 不适合执行时间比较长的服务,所以为了让客户感觉是在“并发”处理而不是“循环”处理,每个请求必须在相对较短时间内执行。

5、reactor + thread per request(过渡方案)

6、reactor + worker thread(过渡方案)

7、reactor + thread pool(能适应密集计算)

muduo库中的/example/suduku/ 中有这样一个例子,因为数独求解是计算密集型任务。

在实践中为了reactor能快速回到事件循环去响应请求,经常将读到的数据put到一个环形内存队列(一般内存or共享内存),而thread pool的线程则从中读取进行数据计算。

8、multiple reactors(能适应更大的突发I/O)

reactors in threads(one loop per thread) reactors in processes

一般来说一个subReactor适用于一个千兆网口

9、multiple reactors + thread pool(one loop per thread + threadpool)(突发I/O与密集计算)

subReactor可以有多个,但threadpool只有一个。

10、proactor服务器(proactor模式,基于异步I/O)

理论上proactor比reactor效率要高一些 异步I/O能够让I/O操作与计算重叠。充分利用DMA特性。 Linux异步IO

glibc aio(aio_*),有bug kernel native aio(io_*),也不完美。目前仅支持 O_DIRECT 方式来对磁盘读写,跳过系统缓存。要自已实现缓存,难度不小。

boost asio实现的proactor,实际上不是真正意义上的异步I/O,底层是用epoll来实现的,模拟异步I/O的。

常见并发服务器方案比较:

三、一些常见问题

1、Linux能同时启动多少个线程?

对于 32-bit Linux,一个进程的地址空间是 4G,其中用户态能访问 3G 左右,而一个线程的默认栈 (stack) 大小是 8M,心算可知,一个进程大约最多能同时启动 350 个线程左右。

2、多线程能提高并发度吗?

如果指的是“并发连接数”,不能。

假如单纯采用 thread per connection 的模型,那么并发连接数大约350,这远远低于基于事件的单线程程序所能轻松达到的并发连接数(几千上万,甚至几万)。所谓“基于事件”,指的是用 IO multiplexing event loop 的编程模型,又称 Reactor 模式。

3、多线程能提高吞吐量吗?

对于计算密集型服务,不能。

如果要在一个8核的机器上压缩100个1G的文本文件,每个core的处理能力为200MB/s,那么“每次起8个进程,一个进程压缩一个文件”与“只启动一个进程(8个线程并发压缩一个文件)”,这两种方式总耗时相当,但是第二种方式能较快的拿到第一个压缩完的文件。

4、多线程能提高响应时间吗?

可以。参考问题3

5、多线程程序日志库要求

线程安全,即多个线程可以并发写日志,两个线程的日志消息不会出现交织。

用一个全局的mutex保护IO 每个线程单独写一个日志文件

前者造成全部线程抢占一个锁(串行写入) 后者有可能让业务线程阻塞在写磁盘操作上。(磁盘IO时间比较长) 解决办法:用一个logging线程负责收集日志消息,并写入日志文件,其他业务线程只管往这个“日志线程”发送日志消息(如通过BlockingQueue提供接口),这称为“异步日志”,也是一个经典的生产者消费者模型。

6、线程池大小的选择

如果池中执行任务时,密集计算所占时间比重为P(0<P<=1),而系统一共有C个CPU,为了让C个CPU跑满而不过载,线程池大小的经验公式T=C/P,即T*P=C(让CPU刚好跑满 )

假设C=8,P=1.0,线程池的任务完全密集计算,只要8个活动线程就能让CPU饱和 假设C=8,P=0.5,线程池的任务有一半是计算,一半是IO,那么T=16,也就是16个“50%繁忙的线程”能让8个CPU忙个不停。

7、线程分类

I/O线程(这里特指网络I/O) 计算线程 第三方库所用线程,如logging,又比如database

参考:

《UNP》

muduo manual.pdf

《linux 多线程服务器编程:使用muduo c++网络库》

http://www.ibm.com/developerworks/cn/linux/l-async/

https://domsch.com/linux/lpc2010/Scaling_techniques_for_servers_with_high_connection%20rates.pdf

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据和云

常识之外:全表扫描为何产生大量 db file sequential read 单块读?

编辑手记:在理解Oracle技术细节时,我们不仅应该读懂概念,还要能够通过测试验证细节,理解那些『功夫在诗外』的部分,例如全表扫描和单块读。 开发人员在进行新系...

3409
来自专栏Java架构沉思录

数据库分库分表如何避免“过度设计”和“过早优化”

关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库、优化索引...

542
来自专栏IT技术精选文摘

从Java视角理解系统结构(一)CPU上下文切换

在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快. 然而实际情况是, 多线程之间...

1829
来自专栏北京马哥教育

分布式与集群的联系与区别

集群是一组协同工作的服务实体,用以提供比单一服务实体更具扩展性与可用性的服务平台。在客户端看来,一个集群就象是一个服务实体,但事实上集群由一组服务实体组成。与单...

2523
来自专栏企鹅号快讯

分布式系统一致性分类,你知道几种?

为了提升系统的可用性、性能、扩展性,我们可以从两个方面着手, 要去建立多个副本。可以放到不同的物理机、机架、机房、地域。一个副本的失效可以让请求转到其他副本。 ...

78710
来自专栏windealli

性能测试知识总结

(下面很多指标术语在不同的语境下可能会有不同的含义,在评价性能指标时,通常是指他们能够达到的最优值。比如吞吐量是指服务能承受的最大吞吐量。)

1222
来自专栏恰同学骚年

《大型网站技术架构》读书笔记之六:永无止境之网站的伸缩性架构

此篇已收录至《大型网站技术架构》读书笔记系列目录贴,点击访问该目录可获取更多内容。

592
来自专栏微信终端开发团队的专栏

微信终端跨平台组件 Mars 系列(三):连接超时与 IP & Port 排序

Mars 系列开始,将为大家介绍 STN(信令传输网络模块)。由于 STN 的复杂性,该模块将被分解为多个篇章进行介绍。本文主要介绍微信中关于 socket 连...

4960
来自专栏java系列博客

=Java面试通关要点汇总集之核心篇参考答案

2043
来自专栏Java架构

如何通过 Java 线程堆栈来进行性能瓶颈分析?

1866

扫码关注云+社区