跨平台的WatiForSingleObject实现

时间:2023-03-09 15:41:28
跨平台的WatiForSingleObject实现

移植win32程序时,有一个难点就是涉及到内核对象的操作,需要模拟win32的实现。

其中比较奇葩的一个是WaitForSingleObject系列。

Linux中没有类似的timeout实现,模拟这个接口,颇费功夫,做个笔记,以备将来。

头文件

 /*
* WIN32 Events for POSIX
* 模拟win32的Event通知等待
*/ #ifndef __LIBROOT_MY_EVENTS_H_
#define __LIBROOT_MY_EVENTS_H_ #if defined(_WIN32) && !defined(CreateEvent)
#error Must include Windows.h prior to including MyEvent.h!
#endif #ifndef WAIT_TIMEOUT
#include <errno.h>
#define WAIT_TIMEOUT ETIMEDOUT
#endif #include <stdint.h> namespace MY_ENVENT
{
#ifdef _WIN32
typedef HANDLE HEVENT;
#else
//Type declarations
struct my_event_t_;
typedef my_event_t_ * HEVENT;
#endif //WIN32-style functions
HEVENT CreateEvent(bool manualReset = false, bool initialState = false,
const CStdString& strEventName = _T(""));
int DestroyEvent(HEVENT event);
int WaitForEvent(HEVENT event, uint64_t milliseconds = -);
int SetEvent(HEVENT event);
int ResetEvent(HEVENT event); int WaitForMultipleEvents(HEVENT *events, int count, bool waitAll, uint64_t milliseconds);
int WaitForMultipleEvents(HEVENT *events, int count, bool waitAll, uint64_t milliseconds, int &index); #ifdef PULSE
int PulseEvent(HEVENT event);
#endif } #endif

使用mutex和condition来模拟

具体实现

/*
* WIN32 Events for Linux
* Linux实现版本
*/
#include "stdafx.h" #ifndef _WIN32 #include "MyEvent.h"
#include <assert.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h> #include <algorithm>
#include <deque> namespace MY_ENVENT
{
struct my_mevent_t_
{
pthread_mutex_t Mutex;
pthread_cond_t CVariable;
pthread_condattr_t CVariable_attr; int RefCount;
union
{
int FiredEvent;
int EventsLeft;
} Status;
bool WaitAll;
bool StillWaiting; void Destroy()
{
pthread_mutex_destroy(&Mutex);
pthread_cond_destroy(&CVariable);
pthread_condattr_destroy(&CVariable_attr);
}
};
typedef my_mevent_t_ *HMEVENT; struct my_mevent_info_t_
{
HMEVENT Waiter;
int WaitIndex;
};
typedef my_mevent_info_t_ *HMEVENT_INFO; struct my_event_t_
{
pthread_cond_t CVariable;
pthread_condattr_t CVariable_attr;
pthread_mutex_t Mutex;
bool AutoReset;
bool State;
std::deque<my_mevent_info_t_> RegisteredWaits;
}; bool RemoveExpiredWaitHelper(my_mevent_info_t_ wait)
{
int result = pthread_mutex_trylock(&wait.Waiter->Mutex); if (result == EBUSY)
{
return false;
} assert(result == ); if (wait.Waiter->StillWaiting == false)
{
--wait.Waiter->RefCount;
assert(wait.Waiter->RefCount >= );
if (wait.Waiter->RefCount == )
{
wait.Waiter->Destroy();
delete wait.Waiter;
}
else
{
result = pthread_mutex_unlock(&wait.Waiter->Mutex);
assert(result == );
} return true;
} result = pthread_mutex_unlock(&wait.Waiter->Mutex);
assert(result == ); return false;
} HEVENT CreateEvent(bool manualReset, bool initialState, const CStdString& strEventName)
{
//unused event name
strEventName.c_str(); HEVENT event = new my_event_t_; pthread_condattr_init(&event->CVariable_attr);
#if _POSIX_MONOTONIC_CLOCK > 0
pthread_condattr_setclock(&event->CVariable_attr, CLOCK_MONOTONIC);
#endif
int result = pthread_cond_init(&event->CVariable, &event->CVariable_attr);
assert(result == ); result = pthread_mutex_init(&event->Mutex, );
assert(result == ); event->State = false;
event->AutoReset = !manualReset; if (initialState)
{
result = SetEvent(event);
assert(result == );
} return event;
} int UnlockedWaitForEvent(HEVENT event, uint64_t milliseconds)
{
int result = ;
if (!event->State)
{
//Zero-timeout event state check optimization
if (milliseconds == )
{
return WAIT_TIMEOUT;
} timespec ts;
if (milliseconds != (uint64_t) -)
{
timeval tv;
gettimeofday(&tv, NULL); uint64_t nanoseconds = ((uint64_t) tv.tv_sec) * * * + milliseconds * * + ((uint64_t) tv.tv_usec) * ; ts.tv_sec = nanoseconds / / / ;
ts.tv_nsec = (nanoseconds - ((uint64_t) ts.tv_sec) * * * );
} do
{
//Regardless of whether it's an auto-reset or manual-reset event:
//wait to obtain the event, then lock anyone else out
if (milliseconds != (uint64_t) -)
{
result = pthread_cond_timedwait(&event->CVariable, &event->Mutex, &ts);
}
else
{
result = pthread_cond_wait(&event->CVariable, &event->Mutex);
}
} while (result == && !event->State); if (result == && event->AutoReset)
{
//We've only accquired the event if the wait succeeded
event->State = false;
}
}
else if (event->AutoReset)
{
//It's an auto-reset event that's currently available;
//we need to stop anyone else from using it
result = ;
event->State = false;
}
//Else we're trying to obtain a manual reset event with a signaled state;
//don't do anything return result;
} int WaitForEvent(HEVENT event, uint64_t milliseconds)
{
int tempResult;
if (milliseconds == )
{
tempResult = pthread_mutex_trylock(&event->Mutex);
if (tempResult == EBUSY)
{
return WAIT_TIMEOUT;
}
}
else
{
tempResult = pthread_mutex_lock(&event->Mutex);
} assert(tempResult == ); int result = UnlockedWaitForEvent(event, milliseconds); tempResult = pthread_mutex_unlock(&event->Mutex);
assert(tempResult == ); return result;
} int WaitForMultipleEvents(HEVENT *events, int count, bool waitAll, uint64_t milliseconds)
{
int unused;
return WaitForMultipleEvents(events, count, waitAll, milliseconds, unused);
} int WaitForMultipleEvents(HEVENT *events, int count, bool waitAll, uint64_t milliseconds, int &waitIndex)
{
HMEVENT wfmo = new my_mevent_t_;
pthread_condattr_init(&wfmo->CVariable_attr);
#if _POSIX_MONOTONIC_CLOCK > 0
pthread_condattr_setclock(&wfmo->CVariable_attr, CLOCK_MONOTONIC);
#endif
int result = ;
int tempResult = pthread_mutex_init(&wfmo->Mutex, );
assert(tempResult == ); tempResult = pthread_cond_init(&wfmo->CVariable, &wfmo->CVariable_attr);
assert(tempResult == ); my_mevent_info_t_ waitInfo;
waitInfo.Waiter = wfmo;
waitInfo.WaitIndex = -; wfmo->WaitAll = waitAll;
wfmo->StillWaiting = true;
wfmo->RefCount = ; if (waitAll)
{
wfmo->Status.EventsLeft = count;
}
else
{
wfmo->Status.FiredEvent = -;
} tempResult = pthread_mutex_lock(&wfmo->Mutex);
assert(tempResult == ); bool done = false;
waitIndex = -; for (int i = ; i < count; ++i)
{
waitInfo.WaitIndex = i; //Must not release lock until RegisteredWait is potentially added
tempResult = pthread_mutex_lock(&events[i]->Mutex);
assert(tempResult == ); //Before adding this wait to the list of registered waits, let's clean up old, expired waits while we have the event lock anyway
events[i]->RegisteredWaits.erase(std::remove_if (events[i]->RegisteredWaits.begin(), events[i]->RegisteredWaits.end(), RemoveExpiredWaitHelper), events[i]->RegisteredWaits.end()); if (UnlockedWaitForEvent(events[i], ) == )
{
tempResult = pthread_mutex_unlock(&events[i]->Mutex);
assert(tempResult == ); if (waitAll)
{
--wfmo->Status.EventsLeft;
assert(wfmo->Status.EventsLeft >= );
}
else
{
wfmo->Status.FiredEvent = i;
waitIndex = i;
done = true;
break;
}
}
else
{
events[i]->RegisteredWaits.push_back(waitInfo);
++wfmo->RefCount; tempResult = pthread_mutex_unlock(&events[i]->Mutex);
assert(tempResult == );
}
} timespec ts;
if (!done)
{
if (milliseconds == )
{
result = WAIT_TIMEOUT;
done = true;
}
else if (milliseconds != (uint64_t) -)
{
timeval tv;
gettimeofday(&tv, NULL); uint64_t nanoseconds = ((uint64_t) tv.tv_sec) * * * + milliseconds * * + ((uint64_t) tv.tv_usec) * ; ts.tv_sec = nanoseconds / / / ;
ts.tv_nsec = (nanoseconds - ((uint64_t) ts.tv_sec) * * * );
}
} while (!done)
{
//One (or more) of the events we're monitoring has been triggered? //If we're waiting for all events, assume we're done and check if there's an event that hasn't fired
//But if we're waiting for just one event, assume we're not done until we find a fired event
done = (waitAll && wfmo->Status.EventsLeft == ) || (!waitAll && wfmo->Status.FiredEvent != -); if (!done)
{
if (milliseconds != (uint64_t) -)
{
result = pthread_cond_timedwait(&wfmo->CVariable, &wfmo->Mutex, &ts);
}
else
{
result = pthread_cond_wait(&wfmo->CVariable, &wfmo->Mutex);
} if (result != )
{
break;
}
}
} waitIndex = wfmo->Status.FiredEvent;
wfmo->StillWaiting = false; --wfmo->RefCount;
assert(wfmo->RefCount >= );
if (wfmo->RefCount == )
{
wfmo->Destroy();
delete wfmo;
}
else
{
tempResult = pthread_mutex_unlock(&wfmo->Mutex);
assert(tempResult == );
} return result;
} int DestroyEvent(HEVENT event)
{
int result = ; result = pthread_mutex_lock(&event->Mutex);
assert(result == );
event->RegisteredWaits.erase(std::remove_if (event->RegisteredWaits.begin(), event->RegisteredWaits.end(), RemoveExpiredWaitHelper), event->RegisteredWaits.end());
result = pthread_mutex_unlock(&event->Mutex);
assert(result == ); result = pthread_cond_destroy(&event->CVariable);
pthread_condattr_destroy(&event->CVariable_attr);
assert(result == ); result = pthread_mutex_destroy(&event->Mutex);
assert(result == ); delete event; return ;
} int SetEvent(HEVENT event)
{
int result = pthread_mutex_lock(&event->Mutex);
assert(result == ); event->State = true; //Depending on the event type, we either trigger everyone or only one
if (event->AutoReset)
{
while (!event->RegisteredWaits.empty())
{
HMEVENT_INFO i = &event->RegisteredWaits.front(); result = pthread_mutex_lock(&i->Waiter->Mutex);
assert(result == ); --i->Waiter->RefCount;
assert(i->Waiter->RefCount >= );
if (!i->Waiter->StillWaiting)
{
if (i->Waiter->RefCount == )
{
i->Waiter->Destroy();
delete i->Waiter;
}
else
{
result = pthread_mutex_unlock(&i->Waiter->Mutex);
assert(result == );
}
event->RegisteredWaits.pop_front();
continue;
} event->State = false; if (i->Waiter->WaitAll)
{
--i->Waiter->Status.EventsLeft;
assert(i->Waiter->Status.EventsLeft >= );
//We technically should do i->Waiter->StillWaiting = Waiter->Status.EventsLeft != 0
//but the only time it'll be equal to zero is if we're the last event, so no one
//else will be checking the StillWaiting flag. We're good to go without it.
}
else
{
i->Waiter->Status.FiredEvent = i->WaitIndex;
i->Waiter->StillWaiting = false;
} result = pthread_mutex_unlock(&i->Waiter->Mutex);
assert(result == ); result = pthread_cond_signal(&i->Waiter->CVariable);
assert(result == ); event->RegisteredWaits.pop_front(); result = pthread_mutex_unlock(&event->Mutex);
assert(result == ); return ;
} //event->State can be false if compiled with WFMO support
if (event->State)
{
result = pthread_mutex_unlock(&event->Mutex);
assert(result == ); result = pthread_cond_signal(&event->CVariable);
assert(result == ); return ;
}
}
else
{
for (size_t i = ; i < event->RegisteredWaits.size(); ++i)
{
HMEVENT_INFO info = &event->RegisteredWaits[i]; result = pthread_mutex_lock(&info->Waiter->Mutex);
assert(result == ); --info->Waiter->RefCount;
assert(info->Waiter->RefCount >= ); if (!info->Waiter->StillWaiting)
{
if (info->Waiter->RefCount == )
{
info->Waiter->Destroy();
delete info->Waiter;
}
else
{
result = pthread_mutex_unlock(&info->Waiter->Mutex);
assert(result == );
}
continue;
} if (info->Waiter->WaitAll)
{
--info->Waiter->Status.EventsLeft;
assert(info->Waiter->Status.EventsLeft >= );
//We technically should do i->Waiter->StillWaiting = Waiter->Status.EventsLeft != 0
//but the only time it'll be equal to zero is if we're the last event, so no one
//else will be checking the StillWaiting flag. We're good to go without it.
}
else
{
info->Waiter->Status.FiredEvent = info->WaitIndex;
info->Waiter->StillWaiting = false;
} result = pthread_mutex_unlock(&info->Waiter->Mutex);
assert(result == ); result = pthread_cond_signal(&info->Waiter->CVariable);
assert(result == );
}
event->RegisteredWaits.clear(); result = pthread_mutex_unlock(&event->Mutex);
assert(result == ); result = pthread_cond_broadcast(&event->CVariable);
assert(result == );
} return ;
} int ResetEvent(HEVENT event)
{
int result = pthread_mutex_lock(&event->Mutex);
assert(result == ); event->State = false; result = pthread_mutex_unlock(&event->Mutex);
assert(result == ); return ;
} #ifdef PULSE
int PulseEvent(HEVENT event)
{
//This may look like it's a horribly inefficient kludge with the sole intention of reducing code duplication,
//but in reality this is what any PulseEvent() implementation must look like. The only overhead (function
//calls aside, which your compiler will likely optimize away, anyway), is if only WFMO auto-reset waits are active
//there will be overhead to unnecessarily obtain the event mutex for ResetEvent() after. In all other cases (being
//no pending waits, WFMO manual-reset waits, or any WFSO waits), the event mutex must first be released for the
//waiting thread to resume action prior to locking the mutex again in order to set the event state to unsignaled,
//or else the waiting threads will loop back into a wait (due to checks for spurious CVariable wakeups). int result = SetEvent(event);
assert(result == );
result = ResetEvent(event);
assert(result == ); return ;
}
#endif
} #endif //_WIN32

C++ Code

win32的实现直接套用win api即可,这里就不贴了。