首先,我们要清楚,什么是并发?什么是多线程?为什么需要多线程?为什么需要并发服务器?
为什么要使用多线程技术
为什么多线程会提高响应速度?
我们知道,线程一般分为:I/O线程(这里特指网络I/O,每个reactor就是一个IO线程);计算线程,==》耗费cpu;第三方库所用线程,如logging,又比如database//这些操作不应该放在计算线程中执行。
例如记录日志,多个线程写日志,由于文件操作比较慢,服务线程会等在IO上,让CPU空闲,增加响应时间。
解决办法:单独用一个logging线程负责收集日志消息,并写入磁盘文件,通过BlockingQueue提供对外接口,别的线程要写日志时,往队列中塞就行,这样服务线程的计算和logging线程的磁盘IO就可以重叠。将耗时的IO操作单独作为一个线程,不去影响其他计算线程的运行,提高其他线程的响应时间。如果异步io成熟的话,可以使用protator模式,(还可以使单个线程io和计算重叠)
多线程能提高并发度吗?
如果指的是“并发连接数”,不能。假如单纯采用 thread per connection 的模型,那么并发连接数大约300,这远远低于基于事件的单线程程序所能轻松达到的并发连接数(几千上万,甚至几万)。所谓“基于事件”,指的是用 IO multiplexing event loop 的编程模型,又称 Reactor 模式。
多线程能提高吞吐量吗?
对于计算密集型服务,不能。如果要在一个8核的机器上压缩100个1G的文本文件,每个core的处理能力为200MB/s,那么“每次起8个进程,一个进程压缩一个文件”与“只启动一个进程(8个线程并发压缩一个文件)”,这两种方式总耗时相当,但是第二种方式能较快的拿到第一个压缩完的文件。
实现方案:
一、循环式/迭代式服务器实现
特点:
每处理完一个请求就关闭连接(短连接),循环式服务器只能使用短连接 (使用长连接不能及时 响应多客户端请求)。此类服务器为单线程应用程序,不能充分利用多核CPU性能。
二、并发式(concurrent)服务器
特点:
1、one connection per process/one connection per thread 一个进程/线程 一个连接 可以处理多个客户端请求
2、适合执行时间比较长的服务
三、预先创建线程/进程 prefork or pre threaded
特点:
1、预先创建若干个子进程,在循环中创建连接。子进程负责与客户端连接。
2、与并发服务器相比,减少了创建线程/进程的开销,提高响应速度
3、但需要解决:客户端连接过来,多个进程都处于accept状态,但只有一个进程返回值正确,其他都失败----惊群
四、反应式(reactive)服务器(reactor模式)
特点:
1、并发处理多个请求。实际上是在一个线程中完成。无法充分利用多核CPU 。
2、不适合执行时间比较长的服务,为了让客户端感觉是在“并发”处理而不是“循环”处理,每个请求必须在相对较短时间内执行。
3、客户端请求过来,首先将监听套接字加入,关注它的可读事件,有可读事件时,acceptor接受连接,接受连接后,返回已连接套接字,加入reactor中,由dispatch分派事件,-->读取-解包-计算-打包-发送。 所有处理都是单线程轮询,在一个线程中完成,不能充分利用多核CPU
五、reactor + thread per request
在方案4基础上,每个轮询过来创建一个线程,这样就可以充分利用多CPU性能。
六、reactor + worker thread
每个连接在一个工作中的线程完成 ---能够充分利用多CPU。但是没有方案2并发式服务器效率高,因为多了reactor事件循环。
七、reactor + thread pool
针对第5种模式reactor + thread per request的改进--能适应密集计算
这里线程池大小的选择:
如果池中执行任务时,密集计算所占时间比重为P(0
假设C=8,P=1.0,线程池的任务完全密集计算,只要8个活动线程就能让CPU饱和
假设C=8,P=0.5,线程池的任务有一半是计算,一半是IO,那么T=16,也就是16个“50%繁忙的线程”能让8个CPU忙个不停。
(当P < 0.2时这个公式不再适合)
八、multiple reactors(能适应更大的突发IO)
特点:
1、reactors in threads(one loop per thread) 事件循环Event Loop 。
2、reactors in processes 每个reactor都是一个进程或者线程。也可以看做一个线程池,线程池数目根据千兆网卡个数确定
3、(轮叫方式round robin)mainReactor将监听套接字加入,每当客户端连接到来,监听套接字产生可读事件,acceptor返回已连接套接字,将已连接套接字加入subReactor中,用其处理客户端连接 再来一个连接,重新分配一个subReactor ,这些连接均匀分配到这些reactor中,每个连接只能在一个reactor线程中处理,一个Reactor一般可以适应一个千兆网口。
九、multiple reactors+thread pool (one loop per thread + threadpool )应对突发IO与密集计算
特点:
1、两个线程池:一个IO线程池、一个处理线程池
2、多个subReactor共享一个线程池 每个subreactor把处理耗时操作放至线程池中处理
十、proactor服务器(proactor模式,基于异步I/O)==》前面的服务器全是同步IO 。
理论上proactor比reactor效率要高一些 。
特点:
1、异步IO能够让IO操作与计算重叠。充分利用了硬件的DMA特性-直接存储访问,不需要CPU干预 。
2、linux异步IO实现方式:
glibc aio(aio_*),有bug
kernel native aio (io_*),也不完美。目前仅支持O_DIRECT方式对磁盘读写,跳过系统缓存,有自己实现缓存,难度不小。 。
3、boost asio实现的proactor,实际上不是真正意义上的异步IO,底层是用epoll来实现的,模拟异步IO的
4、异步IO与同步IO的非阻塞模式的区别:异步IO是不阻塞的,同步IO的非阻塞模式 设置描述符属性:O_NONBLOCK,调用read函数来接收,(如果数据没有准备好,立刻返回,不阻塞),需要一直read -- 》 会处于忙等待状态。
5、异步IO与IO复用区别: IO复用仅仅能够得到通知--内核中有数据了,得到通知后,数据仍然在内核缓冲区中,IO并没有完成,没有到用户空间中,仍然需要调用read函数将数据拉到应用程序缓冲区中。