Thread: SetEvent() and ResetEvent()?

  1. #31
    Malum in se abachler's Avatar
    Join Date
    Apr 2007
    Posts
    3,195
    I generalyl try to avoid dumbing things down too much, pumpkin. That way I at elast avoid talking down to them, even if I risk talking over their head on rare occasions

  2. #32
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Hehehe, well I get your point. And I think the question has been answered, too...
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  3. #33
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Quote Originally Posted by abachler View Post
    You are wrong sugar, WaitForSingleObject() doesnt just take handles, it also takes any other signalable object including events and thread handles.
    WaitForSingleObject() takes a handle. It may be a handle to a process, thread, semaphore, mutex, event, or some another kernel object (I don't think files can be signalled, but I might be wrong), but it's a handle. So your statement above makes exactly zero sense.

    Also, unless Elysia specifically asked for it, stop the cutesy nicknames. They're condescending and potentially even sexist, and you have no business being condescending towards her.



    Events are dangerous.
    Threads under Windows have a very subtle detail: any thread that is currently sleeping, be it in GetMessage, Sleep, WaitForSingleObject or something else, can be woken up by the system in order to perform some tasks. Basically, for whatever reason some part of the system requests that the process execute some function, a so-called APC (asynchronous procedure call). The kernel will then take one of the process's sleeping threads and have it execute the code in question. After that, it puts the thread back in the sleeping state.

    The tricky part about events is that if an event is signaled for a short time, completely contained within the time the should-be-sleeping thread is executing the APC (either due to multiple hardware threads, or the thread losing its timeslice within the APC), the thread will miss the signal.
    In the common scenario where one thread will signal an event and then wait for something, and another thread first waits for the event and then creates the condition where the first thread can continue, this missing of the event can mean deadlock. That's the reason why PulseEvent is deprecated. However, calling SetEvent and ResetEvent in direct succession has exactly the same effect.

    There's a second situation where a thread can miss an event: if it waits for all of multiple objects (WaitForMultipleObjects with the third parameter being TRUE). This situation requires all of the handles to be signaled at the same time. If an event is set, but reset before some other handle is signaled (e.g. a mutex being released), the thread won't be released even if the mutex is then released. Not until the event is signaled again. Unless by then the mutex is locked. This situation can easily lead to starvation. (Starvation is similar to deadlock, but deadlock means that two threads are sort of waiting for each other, whereas starvation "merely" means that a single thread never gets to run because the situation is never right.)

    Therefore, events should be used only in two situations:
    1) Auto-reset events, without ever calling ResetEvent explicitly.
    2) In situations where missing event signals doesn't matter.

    In all other situations, events should be avoided, because they can lead to strange errors. Condition variables should be preferred, but Windows has native condition variables only since Vista, so a non-native implementation (like that of Boost.Thread) should be used instead.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  4. #34
    Malum in se abachler's Avatar
    Join Date
    Apr 2007
    Posts
    3,195
    Quote Originally Posted by CornedBee View Post
    Also, unless Elysia specifically asked for it, stop the cutesy nicknames. They're condescending and potentially even sexist, and you have no business being condescending towards her.
    I am being neither condecending nor sexist, nor do I assume that a female needs me to step up and protect her as if she can't stand up for herself. It is not your place to interject your opinion on the matter. I'm sure anyone who regularly participates on this board is well aware of the fact that Elysia is perfectly capable of voicing her opinion and is not shy about it. Since she has not voiced any objection I can only assume that she see's it exactly as it is intended, as playful, if a bit flirtatious, banter.

  5. #35
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by abachler View Post
    I am being neither condecending nor sexist, nor do I assume that a female needs me to step up and protect her as if she can't stand up for herself. It is not your place to interject your opinion on the matter. I'm sure anyone who regularly participates on this board is well aware of the fact that Elysia is perfectly capable of voicing her opinion and is not shy about it. Since she has not voiced any objection I can only assume that she see's it exactly as it is intended, as playful, if a bit flirtatious, banter.
    Well I found it kind of offensive too, even though I'm not a female. Lets see what Elysia thinks: offensive or not?

  6. #36
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Well, it really doesn't matter to me one way or the other. I'm pretty good at ignoring most things.
    No need to start a fight over it... CornedBee was just kind enough to inform about it.
    Last edited by Elysia; 03-25-2008 at 03:46 PM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  7. #37
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by CornedBee View Post
    Events are dangerous.
    Threads under Windows have a very subtle detail: any thread that is currently sleeping, be it in GetMessage, Sleep, WaitForSingleObject or something else, can be woken up by the system in order to perform some tasks. Basically, for whatever reason some part of the system requests that the process execute some function, a so-called APC (asynchronous procedure call). The kernel will then take one of the process's sleeping threads and have it execute the code in question. After that, it puts the thread back in the sleeping state.

    The tricky part about events is that if an event is signaled for a short time, completely contained within the time the should-be-sleeping thread is executing the APC (either due to multiple hardware threads, or the thread losing its timeslice within the APC), the thread will miss the signal.
    In the common scenario where one thread will signal an event and then wait for something, and another thread first waits for the event and then creates the condition where the first thread can continue, this missing of the event can mean deadlock. That's the reason why PulseEvent is deprecated. However, calling SetEvent and ResetEvent in direct succession has exactly the same effect.

    There's a second situation where a thread can miss an event: if it waits for all of multiple objects (WaitForMultipleObjects with the third parameter being TRUE). This situation requires all of the handles to be signaled at the same time. If an event is set, but reset before some other handle is signaled (e.g. a mutex being released), the thread won't be released even if the mutex is then released. Not until the event is signaled again. Unless by then the mutex is locked. This situation can easily lead to starvation. (Starvation is similar to deadlock, but deadlock means that two threads are sort of waiting for each other, whereas starvation "merely" means that a single thread never gets to run because the situation is never right.)

    Therefore, events should be used only in two situations:
    1) Auto-reset events, without ever calling ResetEvent explicitly.
    2) In situations where missing event signals doesn't matter.

    In all other situations, events should be avoided, because they can lead to strange errors. Condition variables should be preferred, but Windows has native condition variables only since Vista, so a non-native implementation (like that of Boost.Thread) should be used instead.
    Damn... Now I'm not sure whose answer to believe, because it sounds like you're contradicting matsp's answer. If I get some time, I guess I'm just gonna have to create some tests of my own to try to prove which one is right.

  8. #38
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    It is not your place to interject your opinion on the matter.
    I'm a moderator at this board, and therefore it is not only my place but even my duty to bring in my opinion. You would do well to remember that.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  9. #39
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    But that sure was an interesting piece of information about events, CornedBee...
    They look... dangerous.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  10. #40
    Hurry Slowly vart's Avatar
    Join Date
    Oct 2006
    Location
    Rishon LeZion, Israel
    Posts
    6,788
    Therefore, events should be used only in two situations:
    1) Auto-reset events, without ever calling ResetEvent explicitly.
    2) In situations where missing event signals doesn't matter.
    I think the 3rd type situation is also legitimate:
    When the manual event is Reset by the waiting thread (after the wait was complited and some work is done just before entering new wait iteration)
    All problems in computer science can be solved by another level of indirection,
    except for the problem of too many layers of indirection.
    – David J. Wheeler

  11. #41
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    This problem can also occur due to SuspendThread() - which is regularly called while being debugged:
    http://support.microsoft.com/?id=173260

    So to sum up my original "keep in mind" statements on calling SetEvent()-ResetEvent() in succession on a manual event: There's no guarantee that any thread will ever see it signaled.

    Another general "rule of thumb" could be - the thread that sets the event should not be the thread that resets the event. Unless you really know what you're doing - in which case you can throw a goto in there as well

    I'm still waiting to see if cpjust's event was created as an auto or manual reset event.

    gg

  12. #42
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    I'll have to check tomorrow what kind of event it was, but I did this test at home and it looks like CornedBee is right (assuming I didn't screw anything up in this test):
    Code:
    #include <iostream>
    #include <vector>
    #include <bitset>
    #include <windows.h>
    
    // Must set stack size to 1024.
    #define NUM_THREADS        30000
    
    using namespace std;
    
    const wchar_t        g_HandleName[] = L"Test Event";
    bitset<NUM_THREADS>  g_ThreadsRun;
    
    
    DWORD WINAPI ThreadFunc( LPVOID  lpParameter )
    {
        size_t* pParam = static_cast<size_t*>( lpParameter );
        size_t threadNum = *pParam;
        delete pParam;
    
        HANDLE hEvent = OpenEvent( SYNCHRONIZE,      // dwAccess
                                   FALSE,            // bInheritHandle
                                   g_HandleName );
    
        if ( hEvent == NULL )
        {
            cout << "Error opening Event in thread #" << threadNum << "!" << endl;
            return 1;
        }
    
        DWORD ret = WaitForMultipleObjects( 1,          // nCount
                                            &hEvent,    // lpHandles
                                            TRUE,       // bWaitAll
                                            INFINITE );
    
        Sleep( static_cast<DWORD>(threadNum % 10 + 1) );    // Sleep 1-10ms.
        g_ThreadsRun.set( threadNum );
        CloseHandle( hEvent );
        return ret;
    }
    
    
    int main()
    {
        HANDLE hEvent = CreateEvent( NULL,            // lpEventAttr
                                     TRUE,            // bManualReset
                                     FALSE,           // bInitState
                                     g_HandleName );
    
        DWORD dwThreadID = 0;
    
        for ( size_t i = 0; i < NUM_THREADS; ++i )
        {
            HANDLE hThread = CreateThread( NULL,            // lpAttr
                                           0,               // dwStackSize
                                           &ThreadFunc,
                                           new size_t( i ),
                                           0,               // dwCreateFlags
                                           &dwThreadID );
    
            if ( hThread != NULL )
            {
                CloseHandle( hThread );
            }
            else
            {
                cout << "Error creating thread #" << i << "!" << endl;
                return 1;
            }
        }
    
        Sleep( 1000 );
    
        cout << g_ThreadsRun.count() << " out of " << g_ThreadsRun.size() << " threads ran." << endl;
        cout << "Calling SetEvent & ResetEvent()." << endl;
        SetEvent( hEvent );
        ResetEvent( hEvent );
    
        cout << g_ThreadsRun.count() << " out of " << g_ThreadsRun.size() << " threads ran." << endl;
        Sleep( 10000 );
        cout << g_ThreadsRun.count() << " out of " << g_ThreadsRun.size() << " threads ran." << endl;
    
        return 0;
    }
    Results:
    Code:
    0 out of 30000 threads ran.
    Calling SetEvent & ResetEvent().
    28686 out of 30000 threads ran.
    28686 out of 30000 threads ran.

  13. #43
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Quote Originally Posted by vart View Post
    I think the 3rd type situation is also legitimate:
    When the manual event is Reset by the waiting thread (after the wait was complited and some work is done just before entering new wait iteration)
    Perhaps, but then you have a race condition between the setting and the resetting thread - if the setting thread reaches the SetEvent before the resetting event reaches the ResetEvent, the SetEvent is effectively lost.

    Also, this requires that there is only one thread that could possibly wait for the event (not an uncommon situation), in which case making the event auto-reset is probably just easier.

    Edit: Eh, what am I saying? You have this race condition no matter what, unless the setting thread is guaranteed to wait until the other thread has reset the event.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  14. #44
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    Then unsynchronized access to g_ThreadsRun is not good at all. The experiment is also unrealistic with 30 thousands threads. Here's how I would run the experiment:
    Code:
    #include <windows.h>
    #include <iostream>
    #include <numeric>
    using namespace std;
    
    const size_t N_Threads = 3;
    const size_t N_Pulses = 5000;
    
    HANDLE g_evObjs[2],
           &g_evExit = g_evObjs[0], 
           &g_evPulse = g_evObjs[1],
           g_evRestart;
    
    DWORD WINAPI ThreadFunc(void *pParam)
    {
        size_t *pPulse = reinterpret_cast<size_t*>(pParam);
        
        for (;;)
        {
            DWORD status = WaitForMultipleObjects(2, g_evObjs, FALSE, INFINITE);
            if (status == (WAIT_OBJECT_0 + 1)) // pulse event
            {
                ++(*pPulse);
                WaitForSingleObject(g_evRestart, INFINITE);
            }//if
            else if (status == WAIT_OBJECT_0) // exit event
                break; 
            else
                cout << "Unexpected WFMO of " << status << endl;
        }//for
    
        return 0;
    }//ThreadFunc
    
    int main()
    {
        g_evExit = CreateEvent(0, TRUE, FALSE, 0);
        g_evPulse = CreateEvent(0, TRUE, FALSE, 0);
        g_evRestart = CreateEvent(0, TRUE, FALSE, 0);
    
        size_t pulses[N_Threads];
        memset(pulses, 0, sizeof(pulses));
    
        for (size_t i = 0; i < N_Threads; ++i)
        {
            HANDLE hThread = CreateThread(0, 0, &ThreadFunc, pulses + i, 0, 0);
            if (hThread)
                CloseHandle(hThread);
            else
                cout << "CreateThread failed, le " << GetLastError() << endl;
        }//for
    
        // let everyone start up
        Sleep(1000);
        
        size_t loops = N_Pulses;
        while (loops--)
        {
            SetEvent(g_evPulse);
            ResetEvent(g_evPulse);
            
            // this is to prevent threads from spinning on the signaled g_evPulse
            // so we don't get extra pulse counts
            SetEvent(g_evRestart);
            Sleep(1);
            ResetEvent(g_evRestart);
        }//while
    
        Sleep(1000);
    
        cout << "With " << N_Threads << " threads, got " 
             << accumulate(pulses, pulses + N_Threads, size_t(0)) 
             << " pulses, expected "  << N_Threads * N_Pulses 
             << " pulses."  << endl;
        
        SetEvent(g_evExit);
        Sleep(1000);
    
        CloseHandle(g_evPulse);
        CloseHandle(g_evExit);
        CloseHandle(g_evRestart);
    
        return 0;
    }//main
    Single Core:
    Code:
    With 2 threads, got 9994 pulses, expected 10000 pulses.
    With 3 threads, got 14916 pulses, expected 15000 pulses.
    Dual Core:
    Code:
    With 2 threads, got 10000 pulses, expected 10000 pulses.
    With 3 threads, got 14998 pulses, expected 15000 pulses.
    gg
    Last edited by Codeplug; 03-25-2008 at 09:24 PM.

  15. #45
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by Codeplug View Post
    Then unsynchronized access to g_ThreadsRun is not good at all. The experiment is also unrealistic with 30 thousands threads.
    Yeah, I couldn't get it to fail, so I tried bumping up the number of threads. I think what finally got it to fail was when I added the Sleep inside the thread.
    I'll have to study your code in more detail after I get some sleep myself.

Popular pages Recent additions subscribe to a feed