本文共 6149 字,大约阅读时间需要 20 分钟。
特点:
(1)one connection per process/one connection per thread (2)适合执行时间比较长的服务图片如下:
解释: (1)若采用多进程方式 fork之后,父进程需要关闭已连接socket,子进程需要关闭监听socket; 子进程在处理客户端的请求,父进程关闭已连接套接字,等待下一个客户端请求连接,所以可以并发式(concurrent)服务器处理多个客户端的请求,一个客户端一个进程,而且,子进程是长连接的,不断的处理请求,即使子进程中解包,计算,打包的过程时间过长,也不会影响父进程去连接其他客户端的请求。 (2)若采用多线程方式 则不需要关闭什么socket; 主线程每次accept 回来就创建一个子线程服务,由于线程共享文件描述符,故不用关闭。muduo支持
解释
select/poll/epoll就可以实现反应式reactor服务器,即:IO多路复用。 首先将监听socket加入到reactor中,关注其可读事件; 客户端过来的时候,监听socket发生了可读事件,acceptor调用accept接受连接; 接受连接就返回到已连接socket,就将其加入到reactor中,关注其可读事件,已连接socket若发生了可读事件,那么就开始分发dispatch这些事件,读取read请求包,decode解包,计算处理compute,打包encode,最后发送send; 这些任务都是在一个线程完成的,单线程轮询;优缺点:
(1)比并发服务器的并发量高(并发服务器创建的进程数量和线程数量有限),但是不能重复利用多CPU (2)并发处理多个请求,实际上是在一个线程中完成。也就是单线程轮询多个客户端无法充分利用多核CPU 不适合执行时间比较长的服务,所以为了让客户感觉是在“并发”处理而不是“循环”处理,每个请求必须在相对较短时间内执行。当然如果这个请求不能在有限的时间内完成我们可以将这个请求拆分开来,使用有限状态机机制来完成。不过因为有比这种服务器更好的方案,现实生活中即使可以使用有限状态机,也不使用这种服务器。muduo支持
5.reactor+thread per request(过渡方案)的改进
解释:
一个客户端发送了请求包,在reactor线程(也称之I/O线程)中读取请求包,把请求包丢到线程池中处理; 线程池取出一个工作线程来处理请求包:decode,compute,encode,即使compute工作量大,也不影响reactor线程,也可以接受其他客户端连接过来; 能够适用于计算密集型任务,执行时间较长的服务也能适应; 线程池中的线程并不负责数据的发送,要相应数据包还得丢到reactor线程中来send(或者异步调用IO线程的发送方法send来发送);多个事件循环,多个reactor,每个reactor都是一个线程或者进程;只有一个线程来处理网络I/O,可能会出现瓶颈
模式
(1)reactor in threads 每个线程都有一个reactor事件循环EventLoop,muduo推荐 (2)reactor in processes 每个进程都有一个reactors解释
每一个reactor都是一个线程或者进程; mainReactor将监听socket加入进去,每次当client连接过来,监听socket产生可读事件,accepor返回已连接socket; 将已连接socket加入到subReactor中,用它来处理client端的连接; 若再来一个client,按序的将其分配到下一个subReactor; 若只有2个subReactor,若再来一个client,就将其分配到第一个subReactor,这种调度方式,称之为round robin; 它能保证subReactor所处理的连接相对来说比较均匀,每一个连接只能在一个reactor线程中处理,在该线程read,在该线程中send; 当使用一个reactor时,可能该线程会产生瓶颈,使用多个reactor,就能处理突发的I/O请求; 一般来说,一个reactor能够适应一个千兆网口,若服务器有3个千兆网口就分配3个subreactor,再加上mainReactor就是4个; 由于subReactor也是线程,所以可以将其看成是I/O线程池,这些I/O线程池的线程个数一般是固定的,一开始就设定好了,根据千兆网口的数目设置线程数; 若计算compute的请求量过大,还可以这块内容放到线程中计算,这就叫做:multiple reactors+thread pool前面所接收的是基于同步IO
解释
若是回调函数,则该回调函数则是在一个线程中执行的; 我们发现通过异步IO,IO操作和其他处理就能够重叠(I/O与other processing操作可以重叠),就是系统IO操作的同时,应用程序的其他操作也在执行,这个充分利用了硬件的DMA特性,直接存储访问,而不需要CPU的干预;对比
非阻塞的异步IO与同步IO区别: (1)非阻塞同步IO仅仅是给fd增加了O_NONBLOCK属性,然后调用read来接收,若数据没有准备好,则立刻返回,是不阻塞的,这仍然是同步IO,若返回的是EAGAIN或者EWOULDBLOCK,则还需再调用read接收数据,若使用非阻塞的I/O,则会处于忙等状态; (2)与I/O复用的区别: I/O复用仅仅是得到通知,内核中有数据了,你可以读取了,得到通知后,还需要调用read接收数据,得到通知,I/O是并没有完成的,数据仍在内核缓冲区中,并没有拷贝到用户空间的缓冲区,需要调用read将数据从内核缓冲区拉到应用层缓冲区中。 (3)异步IO 一旦I/O完成,内核直接把数据主动推到应用层的缓冲区。aio总结
(1)理论上proactor比reactor效率要高一些 (2)异步I/O能够让I/O操作与计算重叠。充分利用硬件的DMA特性。 (3)Linux异步I/O (4)boost库的asio库中实现的proactor,不是真正的异步IO,而是底层使用epoll来模拟实现的异步IO (5)也就是说linux下的异步IO并不完美; 实际上现实生活中的linux下的网络编程,更多的是使用reactor模式,上面的第9个模式就是最好的模式; windows下的异步IO可以使用完成端口;Linux下主要有两种异步IO,(1)一种是由glibc实现的aio开头的一系列函数、有bug(一般不使用)(2)kernel native aio(io_*), 也不完美。目前仅支持O_DIRECT方式来对磁盘读写,跳过系统缓存。要自己实现缓存,难度不小。man aio_read,man 7 aioaio_read需要指定一块缓冲区,由struct aiocb 结构体来指定int aio_read(struct aiocb *aiocbp); struct aiocb{ int aio_fildes; /* 要被读写的fd */void * aio_buf; /* 读写操作对应的内存buffer */缓冲区__off64_t aio_offset; /* 读写操作对应的文件偏移 */size_t aio_nbytes; /* 需要读写的字节长度 */int aio_reqprio; /* 请求的优先级 */struct sigevent aio_sigevent; /* 异步事件,定义异步操作完成时的通知信号或回调函数 */ //事件通知方式,回调函数需要在线程中执行};
Linux能同时启动多少个线程
对于 32-bit Linux,一个进程的地址空间是 4G,其中用户态能访问 3G 左右,而一个线程的默认栈 (stack) 大小是 10M,心算可知,一个进程大约最多能同时启动 300 个线程左右多线程能提高并发度吗 ?
(1)如果指的是“并发连接数”,不能。 (2)假如单纯采用 thread per connection 的模型,那么并发连接数大约300,这远远低于基于事件的单线程程序所能轻松达到的并发连接数(几千上万,甚至几万)。所谓“基于事件”,指的是用 IO multiplexing event loop 的编程模型,又称 Reactor 模式。多线程能提高 吞吐量吗?
(1)对于计算密集型服务,不能(单位时间的吞吐量是无法提高的)。 (2)如果要在一个8核的机器上压缩100个1G的文本文件,每个core的处理能力为200MB/s,那么“每次起8个进程,一个进程压缩一个文件”与“只启动一个进程(8个线程并发压缩一个文件)”,这两种方式总耗时相当,但是第二种方式能较快的拿到第一个压缩完的文件多线程 如何让 I/O 和计算重叠
(1)多线程程序如何让I/O和计算重叠,降低latency(迟延) (2)例:日志(logging),多个线程写日志,由于文件操作比较慢,服务线程会等在IO上,让CPU空闲,增加响应时间。 (3)解决办法:单独用一个logging线程负责写磁盘文件,通过BlockingQueue提供对外接口,别的线程要写日志的时候往队列一塞就行,这样服务线程的计算和logging线程的磁盘IO就可以重叠。 如果异步I/O成熟的话,可以用protator模式。 (4)protator模式还可以让同一个线程的计算和IO重叠,重复利用DMA特性,执行IO的时候交给DMA,不交给CPU线程池大小的选择
(1)如果池中执行任务时,密集计算所占时间比重为P(0<P<=1),而系统一共有C个CPU,为了让C个CPU跑满而不过载,线程池大小的经验公式T=C/P,即T*P=C(让CPU刚好跑满 (因为只有计算工作耗费CPU,I/O操作可以让DMA去操作,I/O操作不耗费CPU了)) 假设C=8,P=1.0,线程池的任务完全密集计算,只要8个活动线程就能让CPU饱和 假设C=8,P=0.5,线程池的任务有一半是计算,一半是IO,那么T=16,也就是16个“50%繁忙的线程”能让8个CPU忙个不停。 (2)当P<0.2时,需要经过测试得到一个固定的线程数;线程分类
(1)I/O线程(这里特指网络I/O),即reactor模式,每一个reactor模式就是一个I/O线程 (2)计算线程 这个比较耗费CPU (3)第三方库所用线程,如logging,又比如database,这些线程可以单独起来,不要与计算线程混在一起参考:,
转载地址:http://vmiws.baihongyu.com/