在epoll_ctl
上注册一个级别触发的eventfd只会触发一次,当不递减eventfd计数器时。为了总结这个问题,我观察到epoll标志(EPOLLET
,EPOLLONESHOT
或None
用于级别触发行为)的行为类似。或者换句话说:没有效果。
你能确认这个bug吗?
我有一个多线程的应用程序。每个线程都在等待epoll_wait
的新事件,这些事件具有相同的epollfd。如果你想优雅地终止应用程序,所有线程都必须被唤醒。我的想法是使用eventfd计数器(EFD_SEMAPHORE|EFD_NONBLOCK
)为此(用级别触发的epoll行为)一起醒来。(不管少数filedescriptor的雷鸣般的羊群问题。)
例如,对于4个线程,您向eventfd写入4。我希望epoll_wait
立即返回,并一次又一次地返回,直到计数器递减(读取)4次。epoll_wait
每次写入只返回一次。
是的,我仔细阅读了所有相关手册;)
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
static int event_fd = -1;
static int epoll_fd = -1;
void *thread(void *arg)
{
(void) arg;
for(;;) {
struct epoll_event event;
epoll_wait(epoll_fd, &event, 1, -1);
/* handle events */
if(event.data.fd == event_fd && event.events & EPOLLIN) {
uint64_t val = 0;
eventfd_read(event_fd, &val);
break;
}
}
return NULL;
}
int main(void)
{
epoll_fd = epoll_create1(0);
event_fd = eventfd(0, EFD_SEMAPHORE| EFD_NONBLOCK);
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = event_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event_fd, &event);
enum { THREADS = 4 };
pthread_t thrd[THREADS];
for (int i = 0; i < THREADS; i++)
pthread_create(&thrd[i], NULL, &thread, NULL);
/* let threads park internally (kernel does readiness check before sleeping) */
usleep(100000);
eventfd_write(event_fd, THREADS);
for (int i = 0; i < THREADS; i++)
pthread_join(thrd[i], NULL);
}
2条答案
按热度按时间gxwragnw1#
当你写一个
eventfd
时,一个函数eventfd_signal
被调用。它包含下面一行代码,用于唤醒:wake_up_locked_poll
是一个宏:其中
__wake_up_locked_key
定义为:最后,
__wake_up_common
被声明为:注意
nr_exclusive
参数,您将看到写入eventfd
只会唤醒一个独占服务器。exclusive是什么意思?阅读
epoll_ctl
手册页可以让我们了解一些情况:EPOLLEXCLUSIVE(自Linux 4.5起):
为要附加到目标文件描述符fd的epoll文件描述符设置独占唤醒模式。当发生唤醒事件并且多个epoll文件描述符使用
EPOLLEXCLUSIVE
附加到同一目标文件时,一个或多个epoll文件描述符将接收epoll_wait(2)
事件。在添加事件时,您没有使用
EPOLLEXCLUSIVE
,但是要使用epoll_wait
等待,每个线程都必须将自己放入等待队列。函数do_epoll_wait
通过调用ep_poll
来执行等待。通过下面的代码,您可以看到它将当前线程添加到#1903行的等待队列:这就是为什么epoll等待器是 * 独占的 *,所以只有一个线程被唤醒。这个行为已经在v2.6.22-rc 1中引入,相关的变化已经在here中讨论过。
对我来说,这看起来像是
eventfd_signal
函数中的一个bug:在信号量模式中,它应该利用等于所写入的值的nr_exclusive
来执行唤醒。所以你的选择是:
poll
,可能在eventfd
和epoll上都使用evenfd_write
写4次1来分别唤醒每个线程(可能是你能做的最好的)。2w2cym1i2#
在当前的linux版本(例如Ubuntu 22.04 LTS)下,问题中的代码完全可以正常工作。我对它进行了一些编辑,并添加了一些错误检查和时间报告。特别是,应该始终检查
eventfd_read()
的返回代码是否有虚假唤醒:典型输出:
还有一些观察:
THREADS
和WAKE_FIRST
的其他值。eventfd_write(event_fd, WAKE_FIRST)
,事情甚至可以正常工作。eventfd_read()
之前又调用了epoll_wait()
几次,那么这些对epoll_wait()
的重复调用将立即返回。usleep()
之前调用eventfd_read()
,这会导致其他线程的虚假唤醒。(s),这是第一次发出信号,参与阻止系统调用。这是一个很好的功能,而不是一个错误,在我看来。是的,与所有锁定的事情,应当总是检查伪唤醒。