views:

311

answers:

6

I have a main program which creates a collection of N child threads to perform some calculations. Each child is going to be fully occupied on their tasks from the moment their threads are created till the moment they have finished. The main program will also create a special (N+1)th thread which has some intermittent tasks to perform. When certain conditions are met (like a global variable takes on a certain value) the special thread will perform a calculation and then go back to waiting for those conditions to be met again. It is vital that when the N+1th thread has nothing to do, it should not slow down the other processors. Can someone suggest how to achieve this.

EDIT: The obvious but clumsy way would be like this:

// inside one of the standard worker child threads...
if (time_for_one_of_those_intermittent_calculations_to_be_done())
{
    global_flag_set = TRUE;
}

and

// inside the special (N+1)th thread
for(;;)
{
    if (global_flag_set == TRUE)
    {
        perform_big_calculation();
        global_flag_set = FALSE;
    }
    // sleep for a while?
}
+2  A: 

Yes. Use condition variables. If you sleep on a condition variable, the thread will be removed from the runqueue until the condition variable has been signaled.

Michael Aaron Safyan
+7  A: 

You should check out the WaitForSingleObject and WaitForMultipleObjects functions in the Windows API.

WaitForMultipleObjects

Dan Lorenc
I have no doubt that this is the right answer, but Microsoft documentation isn't exactly user friendly, and it almost never gives examples. Do you know of anywhere that I could see an example of the code in action?
Mick
I was able to figure out how to use these last summer during an internship using this example: http://msdn.microsoft.com/en-us/library/ms687055%28VS.85%29.aspx
Dan Lorenc
@Mick: Try http://msdn.microsoft.com/en-us/library/ms686967(VS.85).aspx
Ken White
@Mick: I find the Microsoft documentation is usually programmer friendly, if not user friendly. Look for keywords and go up levels to find summaries in MSDN. You want to "wait" for some "event" so that your threads are "synchronized." So you follow the links around and pop up levels until you come to Synchronization Objects which has a very nice summary of everything useful.
Zan Lynx
+2  A: 

You should use Windows synchronization events for this, so your thread is doing nothing while waiting. See MSDN for more info; I'd start with CreateEvent(), and then go to the rest of the Event-related functions here for OpenEvent(), PulseEvent(), SetEvent() and ResetEvent().

And, of course, WaitForSingleObject() or WaitForMultipleObjects(), as pointed out by mrduclaw in the comment below.

Ken White
These functions with WaitForMultipleObjects/WaitForSingleObject is the way I'd go.
mrduclaw
+2  A: 

A ready-to-use condition class for WIN32 ;)

class Condition {
private:
    HANDLE m_condition;
    Condition( const Condition& ) {} // non-copyable
public:
    Condition() {
        m_condition = CreateEvent( NULL, TRUE, FALSE, NULL );
    }
    ~Condition() {
        CloseHandle( m_condition );
    }
    void Wait() {
        WaitForSingleObject( m_condition, INFINITE );
        ResetEvent( m_condition );
    }
    bool Wait( uint32 ms ) {
        DWORD result = WaitForSingleObject( m_condition, (DWORD)ms );
        ResetEvent( m_condition );
        return result == WAIT_OBJECT_0;
    }
    void Signal() {
        SetEvent( m_condition );
    }
};

Usage:

// inside one of the standard worker child threads...
if( time_for_one_of_those_intermittent_calculations_to_be_done() ) {
    global_flag_set = TRUE;
    condition.Signal();
}


// inside the special (N+1)th thread
for(;;) {
    if( global_flag_set==FALSE ) {
        condition.Wait(); // sends thread to sleep, until signalled
    }
    if (global_flag_set == TRUE) {
        perform_big_calculation();
        global_flag_set = FALSE;
    }
}

NOTE: you have to add a lock (e.g. a critical section) around global_flag_set. And also in most cases the flag should be replaced with a queue or at least a counter (a thread could signal multiple times while 'special' thread is performing its calculations).

frunsi
Fantastic job. I assume it will work (I can't test it right now). Thank you.
Mick
You should edit the example usage to include the use of the lock. Conditions without locks will appear to work, but fail periodically in the manner characteristic of most subtle threading bugs. The example as it stands is dangerous.
quark
I know. And the original class is a class MutexCondition containing a mutex and a condition and has pthread semantics (i.e. a wait() requires a lock() before it). I shortened it for using it here. But well, its bed-time for me.. he can figure it out himself. Or ask me again.
frunsi
@quark: if by "use of the lock" you mean wrapping reads and writes of global_flag_set within Enter/LeaveCriticalSection()'s then that's Ok, I know how to do that.
Mick
A: 

Lacking the more preferred options already given, I generally just yield the CPU in a loop until the desired condition is met.

Ioan
"yield the CPU"... what does that mean in practice?
Mick
Actually, that's exactly what it means in practice. Threads are normally scheduled for equal time within a process, which in turn is scheduled equal time among other processes. Yielding the CPU literally means giving up your scheduled slice because you have nothing to do. Not as efficient as only being scheduled on an event, but works in limited environments without any loss in response time (unlike "sleep"ing).
Ioan
A: 

Basically, you have two possibilities for your N+1th thread.

  • If its work is rare, the best thing to do is simply to ask it to sleep, and wake it up on demand. Rare context switches are insignificants.
  • If it has to work often, then you may need to spinlock it, that is, a busy waiting state that prevent it from being rescheduled, or switched.
Aurélien Vallée