多线程服务器的常见编程模型
IO Models
![](https://img.haomeiwen.com/i12080771/16540b12fd6e6afa.png)
A ->[tcp send buffer] --->[tcp recv buffer]-> B
Blocking IO : A send data 发送缓冲区满或 B recv data接收缓冲区空 会阻塞当前线程
NonBlocking IO: 和Blocking IO不同的是,send或者recv直接返回错误(需要轮询调用)
IO Multiplexing:通过Select/Poll/Epoll等系统调用监控多个连接上的读写事件
Thread Models
Blocking IO + One Thread Per Connection
- Acceptor -> [New Thread]
- 频繁创建销毁线程的时间开销,线程之间的切换也会有大量开销
- 创建大量线程会消耗完系统内存
Blocking IO + Thread Pool (Tomcat BIO)
- Acceptor -> worker thread pool
- 适合短连接,并发数有限
IO Multiplexing + Thread Pool (Tomcat NIO)
- Acceptor->[event queue]->Poller(select/poll/epoll)->worker(one of Thread Pool)
IO Multiplexing + One Thread One Loop *
- 线程数目基本固定
- 高并发
One Thread One Loop思想
一个线程一个循环流程
Loop结构
void CReactor::Run()
{
//线程退出标志
m_bShouldRun = true;
while(m_bShouldRun)
{
//处理其他的任务
handle_other_task();
//利用select/pool/epoll等I/O多路复用监听各个连接(fd)的读写事件
select_or_epoll_function();
//处理各个连接(fd)的读写事件
handle_io_event();
//处理定时器
check_timer();
//处理同步或者异步事件
dispatch_event();
}
}
线程分工(front)
![](https://img.haomeiwen.com/i12080771/cea7189950ee6a4f.jpg)
AcceptReactor
1、监听是否有新的客户端连接(Listenfd上是否有读事件发生)。
2、有新连接则生成新连接的socket(clientfd),并将该socket传递给frontReactor。
Q:线程之间如何传递?
SendEvent Or PostEvent
Q:新连接分配策略?
Round Robin(轮询) 缺点:负载均衡有点问题
TresultReactor
1、订阅tserver发布的流水,接收到流水中的XTP报文,存到TradeResult(CachedFlow)中。
FrontReactor
1、通过Epoll或者Select系统调用监听该线程所负责Socket连接上的读写事件
2、收包(收网络数据,放缓冲区,解包并处理) 发包(....)
3、从TradeResult(cachedFlow)中读取从tserver发布的XTP报文,并处理。
Q:TresultReactor放到TradeResult流中数据,怎么及时让FrontReactor来处理呢?(效率问题)
int nfds = epoll_wait(m_fdEpoll, events, EPOLL_MAX_EVENTS, timeout);
1、timeout设置为0
如果handleothertask没有事做,同时socket没有IO读写事情,线程空转,浪费CPU时间片。
2、timeout设置为10ms
如果没有网络IO时间发生,epoll_wait需要挂起timeout时间才能返回,这样当有其他任务发生,handle_other_task()就会延时处理,TradeResultFlow交易结果流中的数据,frontReactor不能及时处理。
解决方案:
创建eventfd(唤醒fd),并把eventfd绑定到epollfd中,当我们想唤醒直接去执行handle_other_task()时,往eventfd中写入一个字节,该eventfd就变成可读,epoll_wait就立马返回,接下来就可以处理handle_other_task()里面的任务了。
网友评论