在不减少eventfd计数器的情况下,在epoll_ctl上注册级别触发的eventfd只会触发一次。总而言之,我观察到epoll标志(用于级别触发行为的EPOLLETEPOLLONESHOTNone)的行为类似。换句话说:没有效果。

您可以确认此错误吗?

我有一个具有多个线程的应用程序。每个线程等待带有相同epollfd的epoll_wait的新事件。如果要优雅地终止应用程序,则必须唤醒所有线程。我的想法是,为此使用eventfd计数器(EFD_SEMAPHORE|EFD_NONBLOCK)(具有级别触发的epoll行为)来一起唤醒所有对象。 (尽管有少量文件描述符出现雷鸣般的牧群问题。)

例如。对于4个线程,您将4写入eventfd。我期望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);
}

最佳答案

当您写入eventfd时,将调用 eventfd_signal 函数。它包含执行唤醒的以下行:

wake_up_locked_poll(&ctx->wqh, EPOLLIN);

wake_up_locked_poll为宏:
#define wake_up_locked_poll(x, m)                       \
    __wake_up_locked_key((x), TASK_NORMAL, poll_to_key(m))

__wake_up_locked_key定义为:
void __wake_up_locked_key(struct wait_queue_head *wq_head, unsigned int mode, void *key)
{
    __wake_up_common(wq_head, mode, 1, 0, key, NULL);
}

最后, __wake_up_common 被声明为:
/*
 * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
 * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
 * number) then we wake all the non-exclusive tasks and one exclusive task.
 *
 * There are circumstances in which we can try to wake a task which has already
 * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
 * zero in this (rare) case, and we handle it by continuing to scan the queue.
 */
static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode,
            int nr_exclusive, int wake_flags, void *key,
            wait_queue_entry_t *bookmark)

注意nr_exclusive参数,您将看到写入eventfd仅唤醒一个独占的服务员。

排他性是什么意思?阅读 epoll_ctl 手册页可为我们提供一些见解:



添加事件时不使用EPOLLEXCLUSIVE,但要使用epoll_wait等待,每个线程都必须将自己置于等待队列中。函数 do_epoll_wait 通过调用 ep_poll 执行等待。通过执行以下代码,您可以看到它将当前线程添加到了等待队列at line #1903中:
__add_wait_queue_exclusive(&ep->wq, &wait);

这说明了发生的情况-epoll服务员是排他性的,因此只唤醒了一个线程。此行为已在v2.6.22-rc1中引入,相关更改已在here中进行了讨论。

对我来说,这似乎是eventfd_signal函数中的错误:在信号量模式下,应使用等于写入值的nr_exclusive进行唤醒。

因此,您的选择是:
  • 为每个线程创建一个单独的epoll描述符(可能不适用于您的设计-缩放问题)
  • 周围放置一个互斥锁(缩放问题)
  • 可以在poll和epoll上同时使用eventfd
  • 通过用evenfd_write编写1到4次来分别唤醒每个线程(可能是最好的方法)。
  • 09-25 20:57