将Windows手动重置事件移植到Linux?

14

将Windows手动重置事件移植到pthread,除了使用pthread条件变量+ pthread互斥锁+标志指示事件已设置或未设置之外,是否还有更简单的解决方案?

7个回答

18

Pthreads是低级别的构造。不,没有更简单的机制;pthread_cond__*概念上类似于自动重置事件。请注意,pthread_cond_wait可能会出现虚假唤醒,因此无论情况如何,都不应在没有某种外部标志的情况下使用。

不过,构建自己的机制也不是太难。

#include <pthread.h>
#include <stdbool.h>

struct mrevent {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    bool triggered;
};

void mrevent_init(struct mrevent *ev) {
    pthread_mutex_init(&ev->mutex, 0);
    pthread_cond_init(&ev->cond, 0);
    ev->triggered = false;
}

void mrevent_trigger(struct mrevent *ev) {
    pthread_mutex_lock(&ev->mutex);
    ev->triggered = true;
    pthread_cond_signal(&ev->cond);
    pthread_mutex_unlock(&ev->mutex);
}

void mrevent_reset(struct mrevent *ev) {
    pthread_mutex_lock(&ev->mutex);
    ev->triggered = false;
    pthread_mutex_unlock(&ev->mutex);
}

void mrevent_wait(struct mrevent *ev) {
     pthread_mutex_lock(&ev->mutex);
     while (!ev->triggered)
         pthread_cond_wait(&ev->cond, &ev->mutex);
     pthread_mutex_unlock(&ev->mutex);
}

这可能不适合您的使用方式,因为通常您会有一个不同的锁,您想要在 ev->mutex 的位置使用它,但这通常是如何使用的要点。


1
不要忘记,Windows自动重置事件将“记住”它已被发出信号并通知下一个等待的线程,然后重置自身。如果没有线程等待,则pthread_cond_signal实际上可能什么也不做,因此在这种情况下,“事件”似乎并未发生。 - ScaryAardvark
@ephemient:“pthread_cond_wait()和pthread_cond_timedwait()函数用于在条件变量上阻塞。它们在调用线程锁定互斥量时被调用,否则将导致未定义的行为。”难道不应该在每次调用pthread_cond_wait之前捕获互斥量吗?这些函数会原子地释放互斥量并导致调用线程在条件变量cond上阻塞; - user877329
@user877329:pthread_cond_* 函数在等待时会释放互斥锁,并在返回前重新获得它。 - ephemient
@ephemient,非常好的答案。对我来说非常有效。肯定加一。如果我提出了这个问题,我会接受这个答案的。我不知道sfhdhsf有什么问题,因为这个答案完美地回答了他的问题 :) - Pankaj Bansal

7
您可以使用管道轻松实现手动重置事件:
当事件处于触发状态时,从管道中读取数据。
SetEvent -> 写入数据()
ResetEvent -> 读取数据()
WaitForMultipleObjects -> 使用poll()(或select())来检查是否有数据可供读取
"SetEvent"操作应该写入一些内容(例如任意值的1个字节),以使管道处于非空状态,这样后续的"Wait"操作即poll()函数检查是否有数据可供读取就不会阻塞。
"ResetEvent"操作将读取已写入的数据,以确保管道再次为空。
应将管道的读端设置为非阻塞模式,以便尝试从已经重置(空管道)的事件中进行重置(读取)时不会阻塞-fcntl(pipe_out, F_SETFL, O_NONBLOCK)。
由于可能会在ResetEvent之前存在多个SetEvents,因此您应该编写代码,使其读取与管道中的字节数相同的字节数。
char buf[256]; // 256 is arbitrary
while( read(pipe_out, buf, sizeof(buf)) == sizeof(buf));

请注意,等待事件不会从管道中读取数据,因此“事件”会一直保持触发状态,直到进行重置操作。

5
我更喜欢使用管道方法,因为通常不止需要等待一个事件,而是多个对象,例如WaitForMultipleObjects(...)。使用管道可以轻松地将Windows的WaitForMultipleObjects调用替换为poll(...)selectpselectepoll
有一种名为Futex(快速用户空间锁定系统调用)的轻量级进程同步方法。有一个功能futex_fd用于获取一个或多个futexes文件描述符。这个文件描述符与可能代表真实文件、设备、套接字或类似物的许多其他文件描述符一起,可以传递给selectpollepoll。不幸的是,它已经从内核中删除。因此,管道技巧仍然是唯一的工具来完成这个任务。
int pipefd[2];
char buf[256]; // 256 is arbitrary
int r = pipe2(pipefd, O_NONBLOCK);

void setEvent()
{
  write(pipefd[1], &buf, 1); 
}

void resetEvent() {  while( read(pipefd[0], &buf, sizeof(buf)) > 0 ) {;} }

void waitForEvent(int timeoutMS)
{ 
   struct pollfd fds[1];
   fds[0].fd = pipefd[0];
   fds[0].events = POLLRDNORM;
   poll(fds, 1, timeoutMS);
}

// finalize:
close(pipefd[0]);
close(pipefd[1]);

3
没有更简单的解决方案,但是下面的代码可以解决问题:
void LinuxEvent::wait()
{
    pthread_mutex_lock(&mutex);

    int signalValue = signalCounter;

    while (!signaled && signalValue == signalCounter)
    {
        pthread_cond_wait(&condition, &mutex);
    }

    pthread_mutex_unlock(&mutex);
}

void LinuxEvent::signal()
{
    pthread_mutex_lock(&mutex);

    signaled = true;
    signalCounter++;
    pthread_cond_broadcast(&condition);

    pthread_mutex_unlock(&mutex);
}

void LinuxEvent::reset()
{
    pthread_mutex_lock(&mutex);
    signaled = false;
    pthread_mutex_unlock(&mutex);
}

调用signal()时,事件将进入已发信号状态,所有等待线程将运行。然后事件将保持在已发信号状态,所有调用wait()的线程将不会等待。调用reset()将把事件恢复为未发信号状态。

signalCounter是为了在快速发出信号/重置以唤醒等待线程时使用。


2
我们正在寻找一种类似的解决方案,将一些高度多线程的C++代码从Windows移植到Linux,最终编写了一个开源的、MIT许可证的Win32 Events for Linux库。它应该是你正在寻找的解决方案,并且已经经过了性能和资源消耗的严格检验。
它实现了手动和自动重置事件,以及WaitForSingleObjectWaitForMultipleObject功能。

这看起来很不错。遗憾的是,在*nix中描述符、信号量、事件、线程、套接字等不能像在Windows上一样以统一的方式等待它们,其中一个调用可以控制所有东西。 - jww

2

我认为Windows事件更类似于信号量。即,对于自动重置,您可以使用二进制信号量和sem_timedwait()函数。


0

我们(完整披露:我在NeoSmart Technologies工作)编写了一个开源(MIT许可证)库,名为pevents,它在POSIX上实现WIN32手动和自动重置事件,包括WaitForSingleObject和WaitForMultipleObjects克隆。自那以后它已经得到了一些采用(在Linux / Mac上的Steam中使用),并且工作得相当不错。

虽然我个人建议您在POSIX机器上编码时使用POSIX多线程和信号范例,但如果需要,pevents会给您另一个选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接