跨平台的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
C++ Code

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

posted @ 2016-03-02 11:36  Jojodru  阅读(973)  评论(0编辑  收藏  举报