在Linux下,C++中的AutoResetEvent相当于什么?

9

MSDN上的AutoResetEvent描述

我正在尝试将一个在C#下实现的线程池移植到Linux下的C++。但是我不知道应该使用哪些函数来实现与AutoResetEvent类似的行为。

7个回答

12

AutoResetEvent最类似于二进制信号量。那些说"条件变量"的人并没有完全错,但是条件变量用于类似情况,而不是相似的对象。你可以在条件变量之上实现一个(无名)AutoResetEvent:

#include <pthread.h>
#include <stdio.h>

class AutoResetEvent
{
  public:
  explicit AutoResetEvent(bool initial = false);

  ~AutoResetEvent();
  void Set();
  void Reset();

  bool WaitOne();

  private:
  AutoResetEvent(const AutoResetEvent&);
  AutoResetEvent& operator=(const AutoResetEvent&); // non-copyable
  bool flag_;
  pthread_mutex_t protect_;
  pthread_cond_t signal_;
};

AutoResetEvent::AutoResetEvent(bool initial)
: flag_(initial)
{
  pthread_mutex_init(&protect_, NULL);
  pthread_cond_init(&signal_, NULL);
}

void AutoResetEvent::Set()
{
  pthread_mutex_lock(&protect_);
  flag_ = true;
  pthread_mutex_unlock(&protect_);
  pthread_cond_signal(&signal_);
}

void AutoResetEvent::Reset()
{
  pthread_mutex_lock(&protect_);
  flag_ = false;
  pthread_mutex_unlock(&protect_);
}

bool AutoResetEvent::WaitOne()
{
  pthread_mutex_lock(&protect_);
  while( !flag_ ) // prevent spurious wakeups from doing harm
    pthread_cond_wait(&signal_, &protect_);
  flag_ = false; // waiting resets the flag
  pthread_mutex_unlock(&protect_);
  return true;
}

AutoResetEvent::~AutoResetEvent()
{
  pthread_mutex_destroy(&protect_);
  pthread_cond_destroy(&signal_);
}


AutoResetEvent event;

void *otherthread(void *)
{
  event.WaitOne();
  printf("Hello from other thread!\n");
  return NULL;
}


int main()
{
  pthread_t h;
  pthread_create(&h, NULL, &otherthread, NULL);
  printf("Hello from the first thread\n");
  event.Set();

  pthread_join(h, NULL);
  return 0;
}
如果您需要使用命名的自动重置事件,您可能需要查看信号量,并且在翻译代码时可能会遇到稍微困难一些的问题。无论哪种方式,我建议仔细查看您平台上的pthread文档,因为条件变量和自动重置事件不同且行为也不同。

3
我很确定您正在寻找条件变量。这个SO问题的被接受答案:C#中的条件变量 -- 似乎证实了这一点。
请参考此教程,了解POSIX线程中条件变量的详细信息。

1
微软喜欢混淆“库”、“平台”和“语言”的区别,强制你“购买微软”。实际上,.Net中的“AutoResetEvent”类是Win32中“Event”原语的包装器,并类似于PThreads中的“条件变量”。学习Linux中的PThreads。PThreads可以在除Linux外的其他平台上使用,也可以与除C++之外的其他语言(C、Python等)一起使用。 - paulsm4
只是想知道,使用boost:thread库实现会更容易吗? - derekhh
@derekhh:我不知道“更容易”是什么意思,条件变量是一个相当简单的构造,但如果你已经在使用boost::thread(在我看来这是一个完全不错的选择),那么请务必使用它的条件变量包装器,它将与您的其余代码完美契合:http://www.boost.org/doc/libs/1_47_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref - Nicholas Knight

3
条件变量并不等同于AutoResetEvent。它们相当于监视器(Monitors)。这种区别非常重要,如果使用不当可能会导致死锁:
想象一个C#程序中的两个线程A和B。线程A调用WaitOne()函数,线程B调用Set()。如果B在A到达WaitOne()函数之前执行了Set(),那么就没有问题,因为Set()发送给AutoResetEvent()的信号是持久的,它将保持设置状态直到执行WaitOne()。
现在在C语言中,想象两个线程C和D。C调用wait()函数,D调用notify()函数。如果C在D调用notify()函数之前已经在等待,则一切正常。如果C在D调用notify()函数之前未能到达wait()函数,则会发生死锁,因为如果没有人在等待它,信号会丢失,条件变量的状态仍然是“未设置”。
一定要非常小心处理这个问题。

2
您可以使用POSIX互斥锁和条件变量轻松地重新实现Win32 API事件对象。但是,上面的一些评论使我声明以下内容:条件变量与事件对象并不相似。条件变量与事件在根本上是不同的,因为它没有内存或状态。如果在调用pthread_cond_signal或pthread_cond_broadcast时没有任何人被阻塞在条件变量上,那么什么也不会发生,特别是如果稍后有一个线程通过pthread_cond_wait来阻塞,它将被阻塞。我将尝试草拟一个快速的自动重置事件实现。
class event
{
public:
  event(): signalled_ (false) {}

  void signal ()
  {
    std::unique_lock<std::mutex> lock(mutex_);
    signalled_ = true;
    cond_.notify_one ();
  }

  void wait ()
  {
    std::unique_lock<std::mutex> lock(mutex_);

    while (!signalled_)
      cond_.wait (lock);
    signalled_ = false;
  }

protected:
  std::mutex mutex_;
  std::condition_variable cond_;
  bool signalled_;
};

1
Boost的Thread/Condition文档中的示例与常规的ManualResetEvent和AutoResetEvent用法非常相似: http://www.boost.org/doc/libs/1_53_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref (我进行了一些小的编辑以使其更加清晰易懂)
boost::condition_variable cond;
boost::mutex mut;
bool data_ready;

void wait_for_data_to_process()
{
    boost::unique_lock<boost::mutex> lock(mut);
    while(!data_ready)
    {
        cond.wait(lock);
    } 
}

void prepare_data_for_processing()
{
    {   //scope for lock_guard
        boost::lock_guard<boost::mutex> lock(mut);
        data_ready=true;
    }
    cond.notify_one();
}

请注意,条件提供了AutoResetEvent和ManualResetEvent的等待/通知机制,但需要互斥量才能工作。

0

我知道可能有点来不及了,而且我没有有关性能差异的信息,但是使用pthread_kill和sigwait的组合可能是一个可行的替代方案,方法如下:

在适当的地方声明以下内容:

int sigin;
sigset_t sigset;

以以下方式初始化先前的变量:

sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &sigset, null);

在等待线程中,调用sigwait:

sigwait(&sigset, &sigin);

然后,在应该唤醒等待线程的线程上,您可以这样做:

pthread_kill(p_handle, SIGUSR1);

其中p_handle是您希望取消阻塞的线程句柄。

此示例会阻塞等待的线程,直到传递SIGUSR1信号。由于使用pthread_kill,信号仅到达特定线程。


0

嗯,很有可能是一个互斥锁——你有多个调用者想要访问共享资源,但只允许一个进入。在互斥锁的情况下,调用者会尝试获取互斥锁(例如phtread_mutex_lock),执行他们的操作,然后释放(pthread_mutex_unlock),以便其他调用者可以进入。


1
不,它完全不像互斥量。它更像一个条件变量。 - Gabe

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