C中带有工作线程的epoll IO
我正在编写一个小型服务器,它将接收来自多个来源的数据并处理这些数据。接收到的数据源和数据非常重要,但只有epoll能够很好地处理。但是,所有接收到的数据都必须经过分析,并通过大量测试运行,这非常耗时,而且即使epoll多路复用,也会阻塞单个线程。基本上,模式应该如下所示:IO循环接收数据并将其捆绑到作业中,发送到池中可用的第一个线程,该捆绑由作业处理,结果传递给IO循环以写入文件 我决定使用一个IO线程和N个工作线程。使用以下提供的示例,接受tcp连接和读取数据的IO线程很容易实现: 线程通常也很容易处理,但我正在努力以优雅的方式将epoll IO循环与线程池结合起来。我在网上也找不到任何将epoll与工作池一起使用的“最佳实践”,但关于同一主题的问题很多 因此,我有一些问题,希望有人能帮我回答:C中带有工作线程的epoll IO,c,linux,multithreading,posix,epoll,C,Linux,Multithreading,Posix,Epoll,我正在编写一个小型服务器,它将接收来自多个来源的数据并处理这些数据。接收到的数据源和数据非常重要,但只有epoll能够很好地处理。但是,所有接收到的数据都必须经过分析,并通过大量测试运行,这非常耗时,而且即使epoll多路复用,也会阻塞单个线程。基本上,模式应该如下所示:IO循环接收数据并将其捆绑到作业中,发送到池中可用的第一个线程,该捆绑由作业处理,结果传递给IO循环以写入文件 我决定使用一个IO线程和N个工作线程。使用以下提供的示例,接受tcp连接和读取数据的IO线程很容易实现: 线程通常
我的应用程序是Linux专用的,因此我可以使用Linux专用的功能,以便以最优雅的方式实现这一点。不需要跨平台支持,但性能和线程安全是必需的。在我的测试中,每个线程一个epoll实例的性能远远优于复杂的线程模型。如果将侦听器套接字添加到所有epoll实例中,那么工作人员只需
接受(2)
,获胜者将获得连接并在其生命周期内对其进行处理
您的员工可能看起来像这样:
for (;;) {
nfds = epoll_wait(worker->efd, &evs, 1024, -1);
for (i = 0; i < nfds; i++)
((struct socket_context*)evs[i].data.ptr)->handler(
evs[i].data.ptr,
evs[i].events);
}
epoll_add(serv_sock);
while(1){
ret = epoll_wait();
foreach(ret as fd){
req = fd.read();
resp = proc(req);
fd.send(resp);
}
}
我喜欢这个策略,因为:
- 设计非常简单李>
- 所有螺纹都相同李>
- 工作人员和连接是隔离的——没有踩到对方的脚趾或在错误的工作人员中调用
李>read(2)
- 不需要锁(内核开始担心
上的同步问题)李>accept(2)
- 有些自然地负载平衡,因为没有忙碌的工作人员会在
上积极竞争accept(2)
- 使用边缘触发模式,非阻塞插槽,始终读取,直到
李>EAGAIN
- 避免使用
family调用来避免一些意外情况(epoll注册文件描述符,但实际上监视文件描述)李>dup(2)
- 您可以安全地
其他线程的epoll实例李>epoll\u ctl(2)
- 为
使用大型epoll\u wait(2)
缓冲区以避免饥饿struct epoll\u事件
- 使用
保存系统调用李>accept4(2)
- 每个内核使用一个线程(如果CPU绑定,则每个物理线程1个;如果I/O绑定,则每个逻辑线程1个)李>
/poll(2)
在连接计数较低时可能更快select(2)
我希望这会有所帮助。在执行此模型时,因为只有在完全接收到数据包后,我们才知道数据包的大小,不幸的是,我们无法将接收本身卸载到工作线程。相反,我们仍然可以做的最好的事情是使用一个线程来接收数据,该线程必须传递指向完全接收的数据包的指针 数据本身最好保存在一个循环缓冲区中,但是我们希望每个输入源都有一个单独的缓冲区(如果我们得到一个部分数据包,我们可以继续从其他来源接收数据,而不必分割数据。剩下的问题是如何通知工作人员新数据包何时准备好,并给他们一个指向所述数据包中数据的指针。因为这里的数据很少,所以只需要一些指针。最优雅的方法是使用posi消息队列。这些队列为多个发送者和多个接收者提供了写和读消息的能力,始终确保每一条消息都被接收,并且只由一个线程接收 对于每个数据源,您都需要一个类似于下面的结构,我现在将遍历这些字段
struct DataSource
{
int SourceFD;
char DataBuffer[MAX_PACKET_SIZE * (THREAD_COUNT + 1)];
char *LatestPacket;
char *CurrentLocation
int SizeLeft;
};
SourceFD显然是相关数据流的文件描述符,DataBuffer是处理数据包内容的地方,它是一个循环缓冲区
epoll_add(serv_sock);
while(1){
ret = epoll_wait();
foreach(ret as fd){
req = fd.read();
resp = proc(req);
fd.send(resp);
}
}
epoll_add(serv_sock);
epoll_add(queue->fd());
while(1){
ret = epoll_wait();
foreach(ret as fd){
if(fd is worker_thread){
sock, resp = worker->pop_result();
sock.send(resp);
}
if(fd is client_socket){
req = fd.read();
worker->add_task(fd, req);
}
}
}
fd, req = queue->pop_task();
resp = proc(req);
queue->add_result(fd, resp);