方案6是用的最多的单线程reactor方案,方案7和8不是使用的方案只是过渡品,方案9和10是需要重点了解的。
    比较使用的方案在下表突出表示:

    方案 并发模型 阻塞IO 多进程 多线程 IO复用 长连接 并发性 多核 开销 互通性 顺序性 线程数 特点
    1 accept + read/write 同一时间服务一个客户
    2 accept + fork 一个连接一个进程
    3 accept + thread 一个连接一个线程
    4 pre fork
    5 pre thread
    6 select poll epoll 单线程的reactor模式
    7 reactor + thread per task 一个请求一个线程
    8 reactor + worker thread 一个连接一个工作线程,主线程一个
    9 reactor + thread pool 主线程IO,工作线程计算
    10 reactors in threads 每个线程都有一个IO loop
    11 reactors in processes 每个进程一个IO loop,Nginx
    12 reactors in threads+ thread pool 最灵活的IO和CPU配置

    各方案详细介绍如下:

    • 方案1:这并不是并发服务器,只是一个单次单体服务器,一次只能服务一个客户,所以不适合长连接(其他客户还要连接呢)。
    • 方案2:传统的Unix并发网络方案,每次有新的连接请求,就新建一个进程负责处理,适合计算相应的工作量远大于线程创建释放的开销(PostgreSQL使用该方式)
    • 方案3:Java NIO之前的方案,仍然不适合短连接,开销也不小,并且受限于线程数,几千几万个线程会显著增加OS的调度开销。
    • 方案6:基本的单线程Reactor方案,适合IO密集,不适合CPU计算密集的场景(计算量大,耽误下一次poll)。单线程难以发挥多核CPU的能力。处理流程见下图:

    image.png

    • 方案7:过渡方案,在主线程实现reactor监听IO事件,为每一个请求(不是连接)创建线程,在新线程中计算,可以发挥多核CPU能力。需要考虑线程的创建释放开销。
    • 方案8:和方案7类似,不同的是为每个连接创建新线程处理该连接上的所有请求。可能性能还不如方案3
    • 方案9:为弥补方案7频繁的创建销毁线程的缺点,创建固定大小的线程池处理计算任务(乱序响应,无法保证顺序性),仍然在主线程中监听IO事件。处理流程见下图:

    image.png

    • 方案10:muduo和Netty使用的并发网络方案,每个线程都有一个reactor的循环,主线程负责监听accept连接和分发新连接到其他线程,有新连接后在新线程中监听和处理所有请求。与方案9对比,提高了IO事件监听的效率,减少了进出线程池的开销。处理流程见下图:

    image.png

    • 方案11:Nginx使用的方案。如果每个连接之间不需要交互(比如聊天室那种),可以使用进程代替线程。
    • 方案12:方案9和方案10的混合体,即用多个reactor线程监听IO,又用线程池处理复杂计算。

    image.png