Кроссплатформенный эквивалент событий Windows

Я пытаюсь перенести некоторый код Windows в Linux, в идеале через независимые от платформы библиотеки (например, boost), однако я не уверен, как перенести этот фрагмент кода события.

Часть кода включает два потока (давайте назовем их A и B). A хочет сделать что-то, что может сделать только B, поэтому он отправляет B сообщение, а затем ждет, пока B скажет, что все готово. В винде это выглядит примерно так:

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, но, похоже, это означает в сочетании с мьютексом, что не так. здесь.


person Fire Lancer    schedule 04.11.2009    source источник
comment
Я думаю, что ваш специфичный для Windows код использует мьютекс под капотом. Он просто абстрагируется от этого.   -  person rmeador    schedule 05.11.2009
comment
возможный дубликат события ручного сброса Windows, похожего на pthread   -  person jww    schedule 11.02.2014
comment
этот вопрос также содержит полезную информацию   -  person Loomchild    schedule 17.01.2017


Ответы (10)


Я думаю, что хорошим кроссплатформенным эквивалентом событий 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();
}
person Alan    schedule 04.11.2009
comment
Действительно ли для этого требуется мьютекс? Мне определенно не нравится тот факт, что это означает, что B потенциально может заблокироваться в баре (если временной интервал потока A истек между mtxWaitLock и cndSignal.wait). - person Fire Lancer; 05.11.2009
comment
вторая блокировка мьютекса не нужна. Вам не нужно удерживать мьютекс для вызова уведомления (есть ситуации, когда вам это нужно). - person deft_code; 05.11.2009
comment
Как вы указываете время ожидания в случае ожидания, отличного от INFINITE? - person jww; 11.02.2014
comment
с этим связаны две основные проблемы: 1. Существует состояние гонки между запуском рабочего потока и вызовом ожидания в условии. Если поток запускается очень быстро, notify_one может быть вызван до ожидания, и основной поток зависнет. 2. Использование переменной стека может привести к сбою, потому что выход из foo() освобождает условие, и оно может все еще использоваться внутри вызова notify_one. Предлагаемое барьерное решение на самом деле безопаснее, даже несмотря на то, что для этой цели его можно считать излишним. - person gsf; 28.05.2014

Все эти ответы слишком сложны, да ладно, люди, это не так сложно.

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 (что произойдет, если два потока вызовут его одновременно, что произойдет, если один и тот же поток вызовет его дважды). Однако решение будет выглядеть очень похоже на это.

person deft_code    schedule 04.11.2009
comment
Могу я спросить, что здесь bCall? И какие заголовки вы включаете? - person ihdv; 01.03.2021

Вы можете использовать обещание и будущее из потока повышения:

#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);
}
person oofafu    schedule 27.11.2013
comment
обещания/фьючерсы - это только один выстрел, что означает, что вам нужно использовать новый каждый раз, когда вы хотите сигнализировать другому потоку - person deltanine; 08.02.2019

Поскольку комментарии для меня закрыты, мне пришлось разместить свои комментарии к предыдущим постам в качестве ответа. Но на самом деле я не отвечаю.

1) Проблема с решением @Alan. Предоставленный им пример кода работает хорошо. Но это отличается от функций Windows Events. Когда объект события Windows установлен, немедленно возвращается любое количество последующих вызовов WaitForSingleObject, показывая, что объект находится в сигнальном состоянии. Но с решением boost mutex/condition bar() должен уведомлять об условии для каждого foo() вызова, который в этом нуждается. Это значительно усложняет ситуацию для «кросс-платформенных» функций Windows Event. notify_all() тоже не может помочь.

Конечно, это каким-то образом решается в примере кода @deft_code с помощью логической переменной. (Хотя он сам страдает от проблемы состояния гонки. Подумайте, если SetEvent(...) называется мертвым после while(!evt->m_bool) и до evt->m_cond.wait(lock) из отдельного потока. Возникнет взаимоблокировка. Однако это можно решить, используя некоторые методы управления состоянием гонки, чтобы сделать два оператора while() и wait() atomic.) Но у него есть свой недостаток:

2) Также есть проблема с кодом @deft_code при использовании комбинации boost mutex/condition/bool:

Объекты событий в Windows могут иметь имя, что позволяет использовать их для межпроцессной синхронизации. Например, процесс А может создать именованное событие и установить его следующим образом: SetEvent(hFileIsReady). После этого независимо от количества процессов, ожидающих установки этого события (таким образом вызывая WaitForSingleObject(hFileIsReady)), немедленно продолжит свое обычное выполнение до тех пор, пока событие не будет снова сброшено в процессе A ResetEvent(hFileIsReady).

Но комбинация mutex/condition/bool не может позволить себе такой функционал. Конечно, мы можем использовать boost named_condition и named_mutex. Однако как насчет логической переменной, которую мы должны проверить перед ожиданием?

person Masood Khaari    schedule 20.02.2013

Для всех, кто участвует или работает над переносом многопоточного собственного кода Windows C/C++ на Linux/Mac, мы создали библиотеку с открытым исходным кодом (под лицензией MIT), которая реализует как ручной, так и автоматический сброс событий WIN32 поверх pthreads, включая полную реализацию WaitForSingleObject и WaitForMultipleObjects, что делает его единственным известным мне портом WFMO, доступным на Linux/Mac.

pevents доступен на GitHub, прошел боевые испытания и используется некоторыми громкими именами; также где-то плавает буст-порт певентов.

Использование pevents значительно упростит перенос кода из Windows, поскольку лежащие в основе парадигмы сильно различаются между платформами Windows и posix, хотя я бы рекомендовал всем, кто пишет многоплатформенный код, использовать существующую кроссплатформенную библиотеку многопоточности, такую ​​как boost, в первую очередь.

person Mahmoud Al-Qudsi    schedule 11.07.2015
comment
Спасибо!! Я давно искал портативное решение WFMO. Жаль, что стандарт С++ не имеет ничего подобного, AFAIK, boost тоже. Мне определенно пригодится ваша библиотека. - person Michaël Roy; 29.09.2017

вы можете использовать барьер потока повышения

#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();
}
person chub    schedule 04.11.2009
comment
В некоторых случаях это работает, однако в барьере отсутствует значение времени ожидания, которое используется в нескольких местах. - person Fire Lancer; 05.11.2009

Я сделал (или видел) все следующее в разное время для таких вещей:

Используйте мьютекс + переменную условия.

Используйте канал, заставив foo создать канал и передать его конец для записи в bar. Bar затем записывает в трубу, когда bar готов. (Это даже работает с несколькими процессами).

Задайте foo poll для логического значения (да, это плохая идея).

person Michael Kohne    schedule 04.11.2009

Похоже, Вы ищете сигнально-щелевой механизм. Вы можете найти его в:

повышение и Qt

оба кроссплатформенные.

Пример 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);
    }
}
person bua    schedule 04.11.2009
comment
Я не думаю, что механизм усиления сигнала/слота имеет какой-либо механизм ожидания. - person the_mandrill; 05.11.2009
comment
Но с сигналом/слотом я не вижу смысла ждать()? Если что-то готово, просто опубликуйте сообщение, любой заинтересованный клиент предпримет соответствующие действия. Простой шаблон проектирования наблюдателя. - person bua; 05.11.2009
comment
@bua Реализация метода waitForReadyRead - это как минимум одна причина ждать. В этом случае вы должны ждать данных и не должны блокировать события QT. - person baltazar; 13.01.2012


В системах, совместимых с Posix, вы можете использовать Posix IPC. Он используется для обмена сообщениями между процессами и потоками. Если я правильно помню, есть доступный порт cygwin.

person Elmar Weber    schedule 04.11.2009