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
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
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
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.
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.It is not your place to interject your opinion on the matter.
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
I think the 3rd type situation is also legitimate: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.
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
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
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):
Results: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; }
Code:0 out of 30000 threads ran. Calling SetEvent & ResetEvent(). 28686 out of 30000 threads ran. 28686 out of 30000 threads ran.
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
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:
Single Core: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
Dual Core:Code:With 2 threads, got 9994 pulses, expected 10000 pulses. With 3 threads, got 14916 pulses, expected 15000 pulses.
ggCode:With 2 threads, got 10000 pulses, expected 10000 pulses. With 3 threads, got 14998 pulses, expected 15000 pulses.
Last edited by Codeplug; 03-25-2008 at 09:24 PM.