Search code examples
c++visual-c++visual-studio-2012c++11atomic

How to control thread lifetime using C++11 atomics


Following on from this question, I'd like to know what's the recommended approach we should take to replace the very common pattern we have in legacy code.

We have plenty of places where a primary thread is spawing one or more background worker threads and periodically pumping out some work for them to do, using a suitably synchronized queue. So the general pattern for a worker thread will look like this:

There will be an event HANDLE and a bool defined somewhere (usually as member variables) -

HANDLE hDoSomething = CreateEvent(NULL, FALSE, FALSE, NULL);
volatile bool bEndThread = false;

Then the worker thread function waits for the event to be signalled before doing work, but checks for a termination request inside the main loop -

unsigned int ThreadFunc(void *pParam)
{
    // typical legacy implementation of a worker thread
    while (true)
    {
        // wait for event
        WaitForSingleObject(hDoSomething, INFINITE);

        // check for termination request
        if (bEndThread) break;

        // ... do background work ...
    }

    // normal termination
    return 0;
}

The primary thread can then give some work to the background thread like this -

// ... put some work on a synchronized queue ...

// pulse worker thread to do the work
SetEvent(hDoSomething);

And it can finally terminate the worker thread like so -

// to terminate the worker thread
bEndThread = true;
SetEvent(hDoSomething);

// wait for worker thread to die
WaitForSingleObject(hWorkerThreadHandle, dwSomeSuitableTimeOut);

In some cases, we've used two events (one for work, one for termination) and WaitForMultipleObjects instead, but the general pattern is the same.

So, looking at replacing the volatile bool with a C++11 standard equivalent, is it as simple as replacing this

volatile bool bEndThread = false;

with this?

std::atomic<bool> bEndThread = false;

I'm sure it will work, but it doesn't seem enough. Also, it doesn't affect the case where we use two events and no bool.

Note, I'm not intending to replace all this legacy stuff with the PPL and/or Concurrency Runtime equivalents because although we use these for new development, the legacy codebase is end-of-life and just needs to be compatible with the latest development tools (the original question I linked above shows where my concern arose).

Can someone give me a rough example of C++11 standard code we could use for this simple thread management pattern to rewrite our legacy code without too much refactoring?


Solution

  • If it ain't broken don't fix it (especially if this is a legacy code base)

    1. VS style volatile will be around for a few more years. Given that MFC isn't dead this won't be dead any time soon. A cursory Google search says you can control it with /volatile:ms.

    2. Atomics might do the job of volatile, especially if this is a counter there might be little performance overhead.

    3. Many Windows native functions have different performance characteristics when compared to their C++11 implementation. For example, Windows TimerQueues and Multimedia have precision that is not possible to achieve with C++11.

      For example ::sleep_for(5) will sleep for 15 (and not 5 or 6). This can be solved with a mysterious call to timeSetPeriod. Another example is that unlocking on a condition variable can be slow to respond. Interfaces to fix these aren't exposed to C++11 on Windows.