Кроссплатформенный эквивалент событий Windows
Я пытаюсь перенести некоторый код 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, но оказалось, что это означает в сочетании с мьютексом, что не так. Вот.
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();
}
Все эти ответы слишком сложны, давай люди, это не так сложно.
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
(что происходит, если два потока вызывают его одновременно, что происходит, если один и тот же поток вызывает его дважды). Тем не менее, решение будет очень похоже на это.
Вы можете использовать обещание и будущее, из темы повышения:
#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);
}
Для тех, кто занимается или работает над переносом многопоточного собственного кода Windows C/C++ на Linux/Mac, мы создали библиотеку с открытым исходным кодом (MIT-licensed), которая реализует как ручные, так и автоматические сброс событий WIN32 поверх pthreads, включая полную реализацию WaitForSingleObject
а также WaitForMultipleObjects
что делает его единственным из известных мне портов WFMO доступным в Linux / Mac.
pevents доступен на GitHub и был довольно проверен в бою и используется некоторыми громкими именами; есть также бустерный порт pevents, плавающий где-нибудь.
Использование pevents значительно упростит перенос кода из Windows, поскольку основные парадигмы для Windows и posix-платформ существенно отличаются - хотя я бы рекомендовал любому, кто пишет многоплатформенный код, использовать существующую кроссплатформенную многопоточную библиотеку, например, boost.
Поскольку комментарии для меня закрыты, я должен был опубликовать свои комментарии к предыдущим сообщениям в качестве ответа. Но на самом деле я не отвечаю.
1) Есть проблема с решением @Alan. Предоставленный им пример кода работает хорошо. Но это отличается от функциональности Windows Events. Когда установлен объект события Windows, любое количество последующих вызовов WaitForSingleObject
немедленно возвращается, показывая, что объект находится в сигнальном состоянии. Но с бустом mutex
/ condition
решение, bar()
должен уведомить условие для каждого foo()
звонки, которые нужны. Это значительно усложняет ситуацию с "кроссплатформенностью" функциональности Windows Event. notify_all()
тоже не могу помочь.
Конечно, это как-то решается в примере кода @deft_code с помощью логической переменной. (Хотя он страдает от проблемы состояния гонки. SetEvent(...)
называется мертвым после while(!evt->m_bool)
и раньше evt->m_cond.wait(lock)
изнутри отдельной веткой. Будет тупик. Однако это можно решить с помощью некоторых методов управления состоянием гонки, чтобы сделать два утверждения while()
а также wait()
атомный.) Но у него есть свой недостаток:
2) Существует также проблема с кодом @deft_code при использовании boost mutex
/ condition
/ bool
сочетание:
Объекты событий в Windows могут быть названы, что позволяет использовать их для синхронизации между процессами. Например, процесс A может создать именованное событие и установить его так: SetEvent(hFileIsReady)
, Впоследствии, любое количество процессов, ожидающих этого события, будет установлено (вызывая WaitForSingleObject(hFileIsReady)
) немедленно продолжит свое обычное выполнение, пока событие не будет снова сброшено в процессе А путем ResetEvent(hFileIsReady)
,
Но комбинация mutex
/ condition
/ bool
не могу позволить себе такую функциональность. Конечно, мы можем использовать повышение named_condition
а также named_mutex
, Однако, как насчет логической переменной, которую мы должны проверить перед ожиданием?
Вы можете использовать бустер нитевой барьер
#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();
}
Я делал (или видел) все следующее в разное время для таких вещей:
Используйте мьютекс + условную переменную.
Используйте трубу, foo создайте трубу и передайте конец записи в bar. Бар тогда пишет в трубу, когда бар сделан. (Это даже работает многопроцессно).
Проведите foo poll на логическое значение (да, это плохая идея.)
Похоже, вы ищете механизм сигнального слота. Вы можете найти один в:
оба кроссплатформенные.
Пример 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);
}
}
Из Boost.Thread версии 1.47 документации:
Классы condition_variable и condition_variable_any обеспечивают механизм для одного потока, чтобы ожидать уведомления от другого потока о том, что конкретное условие стало истинным.
Под Posix-совместимыми системами вы можете использовать Posix IPC. Он используется для обмена сообщениями между процессами. Если я правильно помню, есть доступный порт Cygwin.