跨平台的WatiForSingleObject实现
移植win32程序时,有一个难点就是涉及到内核对象的操作,需要模拟win32的实现。
其中比较奇葩的一个是WaitForSingleObject系列。
Linux中没有类似的timeout实现,模拟这个接口,颇费功夫,做个笔记,以备将来。
头文件
1 /* 2 * WIN32 Events for POSIX 3 * 模拟win32的Event通知等待 4 */ 5 6 #ifndef __LIBROOT_MY_EVENTS_H_ 7 #define __LIBROOT_MY_EVENTS_H_ 8 9 #if defined(_WIN32) && !defined(CreateEvent) 10 #error Must include Windows.h prior to including MyEvent.h! 11 #endif 12 13 #ifndef WAIT_TIMEOUT 14 #include <errno.h> 15 #define WAIT_TIMEOUT ETIMEDOUT 16 #endif 17 18 #include <stdint.h> 19 20 namespace MY_ENVENT 21 { 22 #ifdef _WIN32 23 typedef HANDLE HEVENT; 24 #else 25 //Type declarations 26 struct my_event_t_; 27 typedef my_event_t_ * HEVENT; 28 #endif 29 30 //WIN32-style functions 31 HEVENT CreateEvent(bool manualReset = false, bool initialState = false, 32 const CStdString& strEventName = _T("")); 33 int DestroyEvent(HEVENT event); 34 int WaitForEvent(HEVENT event, uint64_t milliseconds = -1); 35 int SetEvent(HEVENT event); 36 int ResetEvent(HEVENT event); 37 38 int WaitForMultipleEvents(HEVENT *events, int count, bool waitAll, uint64_t milliseconds); 39 int WaitForMultipleEvents(HEVENT *events, int count, bool waitAll, uint64_t milliseconds, int &index); 40 41 #ifdef PULSE 42 int PulseEvent(HEVENT event); 43 #endif 44 45 } 46 47 #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 == 0); if (wait.Waiter->StillWaiting == false) { --wait.Waiter->RefCount; assert(wait.Waiter->RefCount >= 0); if (wait.Waiter->RefCount == 0) { wait.Waiter->Destroy(); delete wait.Waiter; } else { result = pthread_mutex_unlock(&wait.Waiter->Mutex); assert(result == 0); } return true; } result = pthread_mutex_unlock(&wait.Waiter->Mutex); assert(result == 0); 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 == 0); result = pthread_mutex_init(&event->Mutex, 0); assert(result == 0); event->State = false; event->AutoReset = !manualReset; if (initialState) { result = SetEvent(event); assert(result == 0); } return event; } int UnlockedWaitForEvent(HEVENT event, uint64_t milliseconds) { int result = 0; if (!event->State) { //Zero-timeout event state check optimization if (milliseconds == 0) { return WAIT_TIMEOUT; } timespec ts; if (milliseconds != (uint64_t) -1) { timeval tv; gettimeofday(&tv, NULL); uint64_t nanoseconds = ((uint64_t) tv.tv_sec) * 1000 * 1000 * 1000 + milliseconds * 1000 * 1000 + ((uint64_t) tv.tv_usec) * 1000; ts.tv_sec = nanoseconds / 1000 / 1000 / 1000; ts.tv_nsec = (nanoseconds - ((uint64_t) ts.tv_sec) * 1000 * 1000 * 1000); } 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) -1) { result = pthread_cond_timedwait(&event->CVariable, &event->Mutex, &ts); } else { result = pthread_cond_wait(&event->CVariable, &event->Mutex); } } while (result == 0 && !event->State); if (result == 0 && 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 = 0; 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 == 0) { tempResult = pthread_mutex_trylock(&event->Mutex); if (tempResult == EBUSY) { return WAIT_TIMEOUT; } } else { tempResult = pthread_mutex_lock(&event->Mutex); } assert(tempResult == 0); int result = UnlockedWaitForEvent(event, milliseconds); tempResult = pthread_mutex_unlock(&event->Mutex); assert(tempResult == 0); 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 = 0; int tempResult = pthread_mutex_init(&wfmo->Mutex, 0); assert(tempResult == 0); tempResult = pthread_cond_init(&wfmo->CVariable, &wfmo->CVariable_attr); assert(tempResult == 0); my_mevent_info_t_ waitInfo; waitInfo.Waiter = wfmo; waitInfo.WaitIndex = -1; wfmo->WaitAll = waitAll; wfmo->StillWaiting = true; wfmo->RefCount = 1; if (waitAll) { wfmo->Status.EventsLeft = count; } else { wfmo->Status.FiredEvent = -1; } tempResult = pthread_mutex_lock(&wfmo->Mutex); assert(tempResult == 0); bool done = false; waitIndex = -1; for (int i = 0; i < count; ++i) { waitInfo.WaitIndex = i; //Must not release lock until RegisteredWait is potentially added tempResult = pthread_mutex_lock(&events[i]->Mutex); assert(tempResult == 0); //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], 0) == 0) { tempResult = pthread_mutex_unlock(&events[i]->Mutex); assert(tempResult == 0); if (waitAll) { --wfmo->Status.EventsLeft; assert(wfmo->Status.EventsLeft >= 0); } 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 == 0); } } timespec ts; if (!done) { if (milliseconds == 0) { result = WAIT_TIMEOUT; done = true; } else if (milliseconds != (uint64_t) -1) { timeval tv; gettimeofday(&tv, NULL); uint64_t nanoseconds = ((uint64_t) tv.tv_sec) * 1000 * 1000 * 1000 + milliseconds * 1000 * 1000 + ((uint64_t) tv.tv_usec) * 1000; ts.tv_sec = nanoseconds / 1000 / 1000 / 1000; ts.tv_nsec = (nanoseconds - ((uint64_t) ts.tv_sec) * 1000 * 1000 * 1000); } } 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 == 0) || (!waitAll && wfmo->Status.FiredEvent != -1); if (!done) { if (milliseconds != (uint64_t) -1) { result = pthread_cond_timedwait(&wfmo->CVariable, &wfmo->Mutex, &ts); } else { result = pthread_cond_wait(&wfmo->CVariable, &wfmo->Mutex); } if (result != 0) { break; } } } waitIndex = wfmo->Status.FiredEvent; wfmo->StillWaiting = false; --wfmo->RefCount; assert(wfmo->RefCount >= 0); if (wfmo->RefCount == 0) { wfmo->Destroy(); delete wfmo; } else { tempResult = pthread_mutex_unlock(&wfmo->Mutex); assert(tempResult == 0); } return result; } int DestroyEvent(HEVENT event) { int result = 0; result = pthread_mutex_lock(&event->Mutex); assert(result == 0); event->RegisteredWaits.erase(std::remove_if (event->RegisteredWaits.begin(), event->RegisteredWaits.end(), RemoveExpiredWaitHelper), event->RegisteredWaits.end()); result = pthread_mutex_unlock(&event->Mutex); assert(result == 0); result = pthread_cond_destroy(&event->CVariable); pthread_condattr_destroy(&event->CVariable_attr); assert(result == 0); result = pthread_mutex_destroy(&event->Mutex); assert(result == 0); delete event; return 0; } int SetEvent(HEVENT event) { int result = pthread_mutex_lock(&event->Mutex); assert(result == 0); 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 == 0); --i->Waiter->RefCount; assert(i->Waiter->RefCount >= 0); if (!i->Waiter->StillWaiting) { if (i->Waiter->RefCount == 0) { i->Waiter->Destroy(); delete i->Waiter; } else { result = pthread_mutex_unlock(&i->Waiter->Mutex); assert(result == 0); } event->RegisteredWaits.pop_front(); continue; } event->State = false; if (i->Waiter->WaitAll) { --i->Waiter->Status.EventsLeft; assert(i->Waiter->Status.EventsLeft >= 0); //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 == 0); result = pthread_cond_signal(&i->Waiter->CVariable); assert(result == 0); event->RegisteredWaits.pop_front(); result = pthread_mutex_unlock(&event->Mutex); assert(result == 0); return 0; } //event->State can be false if compiled with WFMO support if (event->State) { result = pthread_mutex_unlock(&event->Mutex); assert(result == 0); result = pthread_cond_signal(&event->CVariable); assert(result == 0); return 0; } } else { for (size_t i = 0; i < event->RegisteredWaits.size(); ++i) { HMEVENT_INFO info = &event->RegisteredWaits[i]; result = pthread_mutex_lock(&info->Waiter->Mutex); assert(result == 0); --info->Waiter->RefCount; assert(info->Waiter->RefCount >= 0); if (!info->Waiter->StillWaiting) { if (info->Waiter->RefCount == 0) { info->Waiter->Destroy(); delete info->Waiter; } else { result = pthread_mutex_unlock(&info->Waiter->Mutex); assert(result == 0); } continue; } if (info->Waiter->WaitAll) { --info->Waiter->Status.EventsLeft; assert(info->Waiter->Status.EventsLeft >= 0); //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 == 0); result = pthread_cond_signal(&info->Waiter->CVariable); assert(result == 0); } event->RegisteredWaits.clear(); result = pthread_mutex_unlock(&event->Mutex); assert(result == 0); result = pthread_cond_broadcast(&event->CVariable); assert(result == 0); } return 0; } int ResetEvent(HEVENT event) { int result = pthread_mutex_lock(&event->Mutex); assert(result == 0); event->State = false; result = pthread_mutex_unlock(&event->Mutex); assert(result == 0); return 0; } #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 == 0); result = ResetEvent(event); assert(result == 0); return 0; } #endif } #endif //_WIN32
win32的实现直接套用win api即可,这里就不贴了。