如何在使用epoll_wait时正确读取数据 我试图将一个使用IOCP的现有Windows C++代码移植到Linux。在决定使用epoll\u wait来实现高并发性之后,我已经面临一个理论问题,即何时尝试处理接收到的数据
想象一下,两个线程调用如何在使用epoll_wait时正确读取数据 我试图将一个使用IOCP的现有Windows C++代码移植到Linux。在决定使用epoll\u wait来实现高并发性之后,我已经面临一个理论问题,即何时尝试处理接收到的数据,c++,linux,multithreading,recv,epoll,C++,Linux,Multithreading,Recv,Epoll,想象一下,两个线程调用epoll\u wait,并收到两条consequetives消息,这样Linux就可以解锁第一个线程,很快就会解锁第二个线程 例如: Thread 1 blocks on epoll_wait Thread 2 blocks on epoll_wait Client sends a chunk of data 1 Thread 1 deblocks from epoll_wait, performs recv and tries to process data Clien
epoll\u wait
,并收到两条consequetives消息,这样Linux就可以解锁第一个线程,很快就会解锁第二个线程
例如:
Thread 1 blocks on epoll_wait
Thread 2 blocks on epoll_wait
Client sends a chunk of data 1
Thread 1 deblocks from epoll_wait, performs recv and tries to process data
Client sends a chunk of data 2
Thread 2 deblocks, performs recv and tries to process data.
这种情况可以想象吗?也就是说,它会发生吗
有没有办法防止这种情况发生,从而避免在recv/处理代码中实现同步?通常,当您有一个线程侦听单个异步源上的数据时,会使用
epoll
。为了避免繁忙的等待(手动轮询),您可以使用epoll
让您知道数据何时准备就绪(很像select
does)
从单个数据源中读取多个线程并不是标准的实践,至少我会认为它是一个坏的实践。
如果要使用多个线程,但只有一个输入源,然后指定其中一个线程来侦听数据并对数据进行排队,以便其他线程可以从队列中读取各个片段。我相信,使用epoll和每个核心一个线程的高性能软件会创建多个epoll句柄,每个句柄处理所有连接的一个子集。通过这种方式,工作被划分,但您描述的问题被避免。如果您有多个线程从同一组epoll句柄读取数据,我建议您使用
EPOLLONESHOT
将epoll句柄置于一次性级别触发模式。这将确保在一个线程观察到触发的句柄之后,在您使用epoll_ctl
重新配置句柄之前,没有其他线程会观察到它
如果需要独立处理读写路径,则可能需要完全拆分读写线程池;一个epoll句柄用于读取事件,一个用于写入事件,并将线程以独占方式分配给其中一个。此外,对读路径和写路径有一个单独的锁。当然,在修改每个套接字的状态时,必须注意读线程和写线程之间的交互
如果您确实使用这种拆分方法,则需要考虑如何处理套接字闭包。很可能您需要一个额外的共享数据锁,以及在共享数据锁下为读写路径设置的“确认关闭”标志。然后,读线程和写线程可以竞相确认,最后一个确认的线程可以清理共享数据结构。就是这样,
void OnSocketClosed(shareddatastructure *pShared, int writer)
{
epoll_ctl(myepollhandle, EPOLL_CTL_DEL, pShared->fd, NULL);
LOCK(pShared->common_lock);
if (writer)
pShared->close_ack_w = true;
else
pShared->close_ack_r = true;
bool acked = pShared->close_ack_w && pShared->close_ack_r;
UNLOCK(pShared->common_lock);
if (acked)
free(pShared);
}
我在这里假设您试图处理的情况如下:
void OnSocketClosed(shareddatastructure *pShared, int writer)
{
epoll_ctl(myepollhandle, EPOLL_CTL_DEL, pShared->fd, NULL);
LOCK(pShared->common_lock);
if (writer)
pShared->close_ack_w = true;
else
pShared->close_ack_r = true;
bool acked = pShared->close_ack_w && pShared->close_ack_r;
UNLOCK(pShared->common_lock);
if (acked)
free(pShared);
}
您有多个(可能非常多个)套接字,希望同时从中接收数据
您希望在第一次接收到线程A上第一个连接的数据时开始处理该数据,然后确保在线程A中处理完该连接的数据之前,不会在任何其他线程上处理该数据
当您这样做时,如果现在在不同的连接上接收到一些数据,您希望线程B拾取该数据并处理它,同时仍然确保在线程B处理完该连接之前没有其他人可以处理该连接,等等
在这种情况下,在多个线程中使用同一个epoll fd的epoll_wait()是一种相当有效的方法(我并不是说它一定是最有效的)
这里的技巧是使用epolonneshot标志将各个连接fd添加到epoll fd。这可以确保一旦从epoll_wait()返回fd,它将不受监视,直到您明确告诉epoll再次监视它为止。这确保处理此连接的线程不会受到干扰,因为在该线程标记要再次监视的连接之前,其他线程都不能处理同一连接
您可以使用epoll_ctl()和epoll_ctl_MOD将fd设置为再次监控EPOLLIN或EPOLLOUT
在多线程中使用这样的epoll的一个显著好处是,当一个线程完成连接并将其添加回epoll监控集中时,仍然在epoll_wait()中的任何其他线程甚至在前一个处理线程返回epoll_wait()之前都会立即监控它。顺便说一句,如果另一个线程现在立即拾取该连接(因此需要获取该连接的数据结构并刷新前一个线程的缓存),这也可能是一个缺点,因为缺少缓存数据局部性。什么最有效取决于您的确切使用模式
如果您试图在不同的线程中处理随后在同一连接上接收到的消息,那么使用epoll的方案将不适合您,使用侦听线程向有效队列馈送工作线程的方法可能更好。前面的回答指出调用epoll\u wait()“从多个线程调用”是一个坏主意,你几乎肯定是对的,但我对这个问题很感兴趣,所以我尝试找出当它从同一个句柄上的多个线程调用,等待同一个套接字时会发生什么。我编写了以下测试代码:
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
struct thread_info {
int number;
int socket;
int epoll;
};
void * thread(struct thread_info * arg)
{
struct epoll_event events[10];
int s;
char buf[512];
sleep(5 * arg->number);
printf("Thread %d start\n", arg->number);
do {
s = epoll_wait(arg->epoll, events, 10, -1);
if (s < 0) {
perror("wait");
exit(1);
} else if (s == 0) {
printf("Thread %d No data\n", arg->number);
exit(1);
}
if (recv(arg->socket, buf, 512, 0) <= 0) {
perror("recv");
exit(1);
}
printf("Thread %d got data\n", arg->number);
} while (s == 1);
printf("Thread %d end\n", arg->number);
return 0;
}
int main()
{
pthread_attr_t attr;
pthread_t threads[2];
struct thread_info thread_data[2];
int s;
int listener, client, epollfd;
struct sockaddr_in listen_address;
struct sockaddr_storage client_address;
socklen_t client_address_len;
struct epoll_event ev;
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener < 0) {
perror("socket");
exit(1);
}
memset(&listen_address, 0, sizeof(struct sockaddr_in));
listen_address.sin_family = AF_INET;
listen_address.sin_addr.s_addr = INADDR_ANY;
listen_address.sin_port = htons(6799);
s = bind(listener,
(struct sockaddr*)&listen_address,
sizeof(listen_address));
if (s != 0) {
perror("bind");
exit(1);
}
s = listen(listener, 1);
if (s != 0) {
perror("listen");
exit(1);
}
client_address_len = sizeof(client_address);
client = accept(listener,
(struct sockaddr*)&client_address,
&client_address_len);
epollfd = epoll_create(10);
if (epollfd == -1) {
perror("epoll_create");
exit(1);
}
ev.events = EPOLLIN;
ev.data.fd = client;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(1);
}
thread_data[0].number = 0;
thread_data[1].number = 1;
thread_data[0].socket = client;
thread_data[1].socket = client;
thread_data[0].epoll = epollfd;
thread_data[1].epoll = epollfd;
s = pthread_attr_init(&attr);
if (s != 0) {
perror("pthread_attr_init");
exit(1);
}
s = pthread_create(&threads[0],
&attr,
(void*(*)(void*))&thread,
&thread_data[0]);
if (s != 0) {
perror("pthread_create");
exit(1);
}
s = pthread_create(&threads[1],
&attr,
(void*(*)(void*))&thread,
&thread_data[1]);
if (s != 0) {
perror("pthread_create");
exit(1);
}
pthread_join(threads[0], 0);
pthread_join(threads[1], 0);
return 0;
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
结构线程信息{
整数;
int插座;
int epoll;
};
void*线程(结构线程信息*arg)
{
结构epoll_事件事件[10];
int-s;
char-buf[512];
睡眠(5*arg->number);
printf(“线程%d开始\n”,参数->编号);
做{
s=epoll\u wait(arg->epoll,事件,10,-1);
如果(s<0){
佩罗(“等待”);
出口(1);
}如果(s==0),则为else{
printf(“线程%d无数据\n”,参数->编号);
出口(1);
}
如果(记录