【问题标题】:Cross-Platform equivalent to windows events相当于windows事件的跨平台
【发布时间】:2010-12-13 05:17:45
【问题描述】:

我正在尝试将一些 Windows 代码移植到 Linux,最好是通过与平台无关的库(例如 boost),但是我不确定如何移植这段事件代码。

这段代码涉及两个线程(我们称它们为 A 和 B)。 A 想做一些只有 B 可以做的事情,所以它给 B 发送了一条消息,然后等待 B 说它完成了。在 Windows 中,这看起来像:

void foo();//thread a calls this
void bar(HANDLE evt);

void foo()
{
    HANDLE evt = CreateEvent(0,FALSE,FALSE,0);
    bCall(boost::bind(&bar, evt));
    WaitForSingleObject(evt,INFINITE);
    CloseHandle(evt);
}
void bar(HANDLE evt)
{
    doSomething();
    SetEvent(evt);
}

我查看了 boost::thread 库,但它似乎没有任何功能,我可以看到的关闭是 boost::condition_variable,但它似乎是与互斥锁结合使用的意思,即这里不是这样。

【问题讨论】:

标签: c++ windows multithreading boost cross-platform


【解决方案1】:

所有这些答案都太复杂了,来吧人们,这并不难。

namespace porting
{
   class Event;
   typedef Event* Event_handle;
   static const unsigned k_INFINITE = 0xFFFFFFFF;

   class Event
   {
      friend Event_handle CreateEvent( void );
      friend void CloseHandle( Event_handle evt );
      friend void SetEvent( Event_handle evt );
      friend void WaitForSingleObject( Event_handle evt, unsigned timeout );

      Event( void ) : m_bool(false) { }

      bool m_bool;
      boost::mutex m_mutex;
      boost::condition m_condition;
   };

   Event_handle CreateEvent( void )
   { return new Event; }

   void CloseHandle( Event_handle evt )
   { delete evt; }

   void SetEvent( Event_handle evt )
   {
      evt->m_bool = true;
      evt->m_cond.notify_all();
   }

   void WaitForSingleObject( Event_handle evt, unsigned timeout )
   {
      boost::scoped_lock lock( evt->m_mutex );
      if( timeout == k_INFINITE )
      {
         while( !evt->m_bool )
         {
            evt->m_cond.wait( lock );
         }
      }
      else
      {
         //slightly more complex code for timeouts
      }
   }

}// porting

void foo()
{
   porting::Event_handle evt = porting::CreateEvent();
   bCall( boost::bind(&bar, evt ) );
   porting::WaitForSingleObject( evt, porting::k_INFINITE );
   porting::CloseHandle(evt);
}

void bar( porting::Event_handle evt )
{
   doSomething();
   porting::SetEvent(evt);
}

由于我不熟悉 WaitForSingleObject 的语义(如果两个线程同时调用它会发生什么,如果同一个线程调用它会发生什么),可能还需要做更多工作才能使其完全正常工作两次)。但是,解决方案看起来很像这样。

【讨论】:

  • 请问这里的bCall 是什么?你包括哪些标题?
【解决方案2】:

我认为一个好的跨平台等效于 win32 事件是 boost::condition,所以你的代码可能看起来像这样:

void foo()
{
    boost::mutex mtxWait; 
    boost::condition cndSignal;

    bCall(boost::bind(&bar, mtxWait, cndSignal));

    boost::mutex::scoped_lock mtxWaitLock(mtxWait);
    cndSignal.wait(mtxWait); // you could also use cndSignal.timed_wait() here
}

void bar(boost::mutex& mtxWait, boost::condition& cndSignal)
{
    doSomething();
    cndSignal.notify_one();
}

【讨论】:

  • 这真的需要互斥锁吗?我当然喜欢它意味着 B 可能会阻塞在 bar 中的事实(如果线程 A 的时间片在 mtxWaitLock 和 cndSignal.wait 之间过期)。
  • 不需要第二个互斥锁。您无需持有互斥锁即可调用 notify(在某些情况下您需要这样做)。
  • 在非无限等待的情况下如何指定超时时间?
  • 这有两个主要问题: 1. 在启动工作线程和在条件中调用等待之间存在竞争条件。如果线程启动得非常快,可以在等待和主线程挂起之前调用 notify_one。 2. 使用堆栈变量可能会导致崩溃,因为退出 foo() 释放了条件,并且它可能仍在 notify_one 调用中使用。建议的屏障解决方案实际上更安全 - 即使出于此目的它可能被认为是矫枉过正。
【解决方案3】:

您可以使用来自 boost 线程的承诺和未来:

#include <boost\thread.hpp>

boost::promise<bool> prom;

void foo()
{
    auto future = prom.get_future();
    auto result = future.wait_for(boost::chrono::milliseconds(1000));
    // we get here if (a) 1 second passes or (b) bar sets the promise value
    if (result==boost::future_status::ready) 
    { 
        /* bar set the promise value */ 
    }
    if (result==boost::future_status::timeout)
    {
        /* 1 second passed without bar setting promise value */ 
    }
}

void bar()
{
    prom.set_value(true);
}

【讨论】:

  • promises/futures 是一次性的,这意味着你每次想要向另一个线程发信号时都需要使用一个新的
【解决方案4】:

由于 cmets 对我关闭,我不得不将我的 cmets 发布到以前的帖子作为答案。但实际上我没有回答。

1) @Alan 的解决方案有问题。 他提供的示例代码运行良好。但它不同于 Windows 事件功能。当 Windows 事件对象被设置时,对WaitForSingleObject任意数量的 后续调用立即返回,表明该对象处于信号状态。但是使用 boost 的 mutex/condition 解决方案,bar() 必须通知每个需要它的 foo() 调用的条件。这使得“跨平台”Windows 事件功能的情况变得更加困难。 notify_all()也帮不上忙。

当然,这在@deft_code 的示例代码中通过使用布尔变量以某种方式解决。 (尽管它本身会遇到竞争条件问题。考虑是否在 while(!evt-&gt;m_bool) 之后和 evt-&gt;m_cond.wait(lock) 之前从单独的线程中调用 SetEvent(...) 死锁。会发生死锁。但这可以通过使用一些竞争条件管理技术来解决使while()wait()这两个语句原子。)但它有自己的缺点:

2) @deft_code 的代码在使用 boost mutex/condition/bool 组合时也存在问题:

Windows 中的事件对象可以命名,这使它们能够用于进程间同步。例如,进程 A 可以创建一个命名事件并将其设置为:SetEvent(hFileIsReady)。之后,等待设置此事件(从而调用WaitForSingleObject(hFileIsReady))的任何数量的进程将立即继续正常执行,直到ResetEvent(hFileIsReady) 在进程A 中再次重置该事件。

但是mutex/condition/bool的组合不能提供这样的功能。当然,我们可以使用 boost named_conditionnamed_mutex。但是,我们必须在等待之前检查的布尔变量呢?

【讨论】:

    【解决方案5】:

    对于参与或致力于将多线程原生 Windows C/C++ 代码移植到 Linux/Mac 的任何人,we've authored an open source (MIT-licensed) library 在 pthread 之上实现手动和自动重置 WIN32 事件,包括 @987654323 的完整实现@ 和 WaitForMultipleObjects,使其成为我所知道的 Linux/Mac 上唯一可用的 WFMO 端口。

    pevents is available on GitHub 已经过实战考验,并被一些知名人士使用;还有一个pevents的增强端口漂浮在某个地方。

    使用 pevents 将使从 Windows 移植代码变得更加容易,因为 Windows 和 posix 平台之间的底层范例有很大的不同——尽管我鼓励任何编写多平台代码的人使用现有的跨平台多线程库,例如第一名。

    【讨论】:

    • 谢谢!!多年来,我一直在寻找便携式 WFMO 解决方案。很遗憾,c++ 标准没有任何接近它的东西,AFAIK,boost 也没有。我肯定对你的图书馆有一些直接的用途。
    【解决方案6】:

    你可以使用boost线程barrier

    #include <boost/thread/thread.hpp>
    #include <boost/thread/barrier.hpp>
    #include <iostream>
    
    void foo(boost::barrier* b)
    {
      std::cout << "foo done" << std::endl;
      b->wait();
    }
    
    
    int main()
    {
      std::cout << "start foo" << std::endl;
      boost::barrier b(2);
    
      boost::thread t(&foo, &b);
      b.wait();
      std::cout << "after foo done" <<  std::endl;
      t.join();
    }
    

    【讨论】:

    • 这在某些情况下有效,但是屏障缺少在几个地方使用的超时值。
    【解决方案7】:

    我在不同的时间做过(或见过)以下所有类似的事情:

    使用互斥体 + 条件变量。

    使用管道,让 foo 创建管道并将其写入端传递给 bar。然后,当 bar 完成后,Bar 会写入管道。 (这甚至适用于多进程)。

    对布尔值进行 foo 投票(是的,这是个坏主意。)

    【讨论】:

      【解决方案8】:

      看起来您正在寻找信号槽机制。您可以在以下位置找到:

      boostQt

      都是跨平台的。

      Qt 示例:

       #include <QObject>
      
       class Counter : public QObject
       {
           Q_OBJECT
       public:
           Counter() { m_value = 0; }
      
           int value() const { return m_value; }
      
       public slots:
           void setValue(int value);
      
       signals:
           void valueChanged(int newValue);
      
       private:
           int m_value;
       };
      
      Counter a, b;
      QObject::connect(&a, SIGNAL(valueChanged(int)),
                        &b, SLOT(setValue(int)));
      
      a.setValue(12);     // a.value() == 12, b.value() == 12
      b.setValue(48);     // a.value() == 12, b.value() == 48
      
      void Counter::setValue(int value)
      {
          if (value != m_value) {
              m_value = value;
              emit valueChanged(value);
          }
      }
      

      【讨论】:

      • 我不认为升压信号/槽机制有任何等待机制
      • 但是对于信号/插槽我没有理由等待()?如果东西准备好了,只需发布​​消息,任何感兴趣的客户都会采取适当的行动。简单的观察者设计模式。
      • @bua 实现waitForReadyRead 方法至少是等待的一个理由。在这种情况下你必须等待数据并且你不应该阻塞 QT 事件。
      【解决方案9】:

      来自 Boost.Thread 1.47 版documentation

      condition_variable 和 condition_variable_any 类提供了一个 一个线程等待另一个线程通知的机制 一个特定的条件已经成立。

      【讨论】:

        【解决方案10】:

        在符合 Posix 的系统下,您可以使用 Posix IPC。它用于进程间/线程间消息传递。如果我没记错的话,有一个 cygwin 端口可用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多