Skip to content

Latest commit

 

History

History
28 lines (15 loc) · 2.93 KB

并发模型.md

File metadata and controls

28 lines (15 loc) · 2.93 KB

并发模型

WebServer 采用 Reactor 模式。在使用多线程提高程序并发的同时,为了避免线程频繁创建和销毁带来的开销,使用线程池的模式,在 server 启动的时候创建指定数量的线程。

I/O 多路复用的实现方式为 Epoll ET。

工作方式

Server 中有一个 MainReactor,负责响应 client 的连接请求,并建立连接。它是用一个NIO Selector。在建立连接后用 Round Robin 的方式分配给某个 SubReactor,因为涉及到跨线程的任务分配,需要加锁。这里的锁由某个特定线程中的 EventLoop 创建,只会被该线程和主线程竞争。

SubReactor 可以有一个或多个,每一个 SubReactor 都会在一个独立线程中运行,并且维护一个独立的 NIO Selector。

当主线程将新连接分配到某个 SubReactor 之后,该线程可能正阻塞在 poller 的等待中。所以,使用eventfd进行异步唤醒,线程会从 poller_wait 中醒来,得到活跃事件,进行处理。连接的响应使用了muduo中的queueInLoop,用来绑定连接的回调函数并执行。

Epoll 工作方式

Epoll 采用了边缘触发的模式,即每次可读事件的处理需要一次性处理完毕(读到 EAGAIN),每次写事件也是如此。这样做主要是为了减少系统调用的次数,提高 Epoll 的工作效率。

并发工作流

  1. Server 的 MainReactor 本身是一个 EventLoop,在初始化 Server 对象的时候,注册一个监听socket,并使用这个socket创建acceptChannel注册到 mainLoop中。并且, Server 会创建一个 EventLoop的线程池,在后面以轮转的方式把channel注册到上面。

  2. acceptChannel 会绑定一个 handleNewConn 的处理函数,用于新连接的处理。其大致逻辑是:在监听socket收到连接事件后,会从让 mainLoop 中的 poller 从 epoll_wait 中醒来,调用绑定的 handleNewConn 函数。

    在 handleNewConn 中,对于新accept的socket,会创建一个 HttpData 对象以及与之绑定的 channel,注册到当前轮转到的 EventLoop 上,后续的工作逻辑就交给负责的工作线程去处理。

  3. 对于目前实现的 http 请求处理方法,都在 HttpData 类中实现。Channel 在收到事件的时候调用的就是里面实现的读写处理方法。但是目前有一个问题,就是连接事件到来的时候,事件所在的 eventloop 是如何感知到的? 实现逻辑是:在注册事件的时候,将channel的newEvent方法放入所属loop的处理队列中去。但是这个时候连接的socket并没有被加入到poller里面,而这个时候poller很有可能阻塞在 epoll_wait,而无法处理新到来的请求。所以这里对于每个loop设置了一个关联fd为eventfd的wakeupChannel。在有新连接到来的时候,会对eventfd进行一次写数据的操作。这样,epoll_wait 就能感知到事件的发生,从poll过程中醒来,处理loop上面的事件。