2

I am relatively new to threads, and I'm still learning best techniques and the C++11 thread library. Right now I'm in the middle of implementing a worker thread which infinitely loops, performing some work. Ideally, the main thread would want to stop the loop from time to time to sync with the information that the worker thread is producing, and then start it again. My idea initially was this:

// Code run by worker thread
void thread() {
    while(run_) {
        // Do lots of work
    }
}
// Code run by main thread
void start() {
    if ( run_ ) return;
    run_ = true;
    // Start thread
}
void stop() {
    if ( !run_ ) return;
    run_ = false;
    // Join thread
}
// Somewhere else
volatile bool run_ = false;

I was not completely sure about this so I started researching, and I discovered that volatile is actually not required for synchronization and is in fact generally harmful. Also, I discovered this answer, which describes a process nearly identical to the one I though about. In the answer's comments however, this solution is described as broken, as volatile does not guarantee that different processor cores readily (if ever) communicate changes on the volatile values.

My question is this then: Should I use an atomic flag, or something else entirely? What exactly is the property that is lacking in volatile and that is then provided by whatever construct is needed to solve my problem effectively?

Community
  • 1
  • 1
Svalorzen
  • 5,353
  • 3
  • 30
  • 54
  • Your program has a data race, so indeed, it is horribly broken. Use a `std::atomic` or something like that. (Maybe an `std::atomic_flag`.) – Kerrek SB Dec 17 '13 at 17:32
  • @KerrekSB Could you possibly explain why it has a data race? The worker thread is only reading the shared variable, and I do not access any data while the thread is running. – Svalorzen Dec 17 '13 at 17:35
  • 1
    Because the language says so. You have more than one access, at least one of which is a write, to the same memory location without any ordering, and the memory location is not an `std::atomic`. – Kerrek SB Dec 17 '13 at 17:36
  • @KerrekSB I think I understand. Can you help me see what could go wrong, and why is ordering important here? When the main thread stops the worker it doesn't actually need it to stop immediately, as long as it will stop. Can the boolean become corrupted/not change due to the non atomicity of the assignment? – Svalorzen Dec 17 '13 at 17:42
  • 2
    The store to your boolean variable could, legally, never become visible to the worker thread. The `volatile` keyword prohibits the _compiler_ from omitting the (apparently redundant) load, but the CPU is still allowed to keep loading the same stale value from cache. You need `std::atomic` (or a mutex) to guarantee the CPU & cache hardware do what you need. – Useless Dec 17 '13 at 17:54
  • @Useless However, even using an atomic does not guarantee that, right? I would need to use the correct memory order options in both the read and the write, or is that unnecessary? – Svalorzen Dec 17 '13 at 18:01
  • True - the atomicity guarantee might be required anyway, but it seems relatively unlikely for `bool` - the memory ordering options are what I was thinking of in the comment above. – Useless Dec 17 '13 at 18:07
  • @Useless If you feel like making this an answer I'll be glad to accept it. – Svalorzen Dec 17 '13 at 18:09
  • I could, but not in this comment space. Watch [this video](http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2) (and part 2) before anything else. – Kerrek SB Dec 17 '13 at 18:37
  • @KerrekSB I saw the whole video, very informative. In addition part 2 actually mentioned my exact same problem, and though it advised to have relaxed reading within the worker thread, I'm not sure it said explicitly the type of operation upon store in the main thread. That would be a release, right? – Svalorzen Dec 18 '13 at 00:59

3 Answers3

2

Have you looked for the Mutex ? They're made to lock the Threads avoiding conflicts on the shared data. Is it what you're looking for ?

tho
  • 130
  • 8
  • Since I would be accessing shared data only when the worker thread is not running, I don't think I actually need a mutex. For me the problem is simply managing to stop the thread, and after that I can perform whatever synching I need. – Svalorzen Dec 17 '13 at 17:34
  • you can't know when the OS will schedule or stop a thread, so the logic would become difficult as you need to introduce some sort of event handlers that is ran when all the thread has finally stopped running – lolski Dec 17 '13 at 17:36
  • You want it to stop it definitely ? If so : http://msdn.microsoft.com/en-us/library/windows/desktop/ms682659%28v=vs.85%29.aspx Well, that's a function for Win Threads. – tho Dec 17 '13 at 17:38
  • I would assume he wants a portable, C++11 compliant solutions. also check [this](http://stackoverflow.com/a/2485177/1622847) for explanation why volatile alone isn't enough. – lolski Dec 17 '13 at 17:40
  • Portable would be better, yes. In addition a new thread can be spawned afterwards, I just need to access the data the worker is using while the worker is shut off, basically. – Svalorzen Dec 17 '13 at 17:43
  • Have you tried looking for the equivalent of the Conditional Variables for posix threads ? http://www.cs.mtu.edu/~shene/NSF-3/e-Book/MONITOR/CV.html I've never worked with other threads than POSIX's so I'm not really sure it exists elsewhere. – tho Dec 17 '13 at 17:46
1

I think you want to use barrier synchronization using std::mutex?

Also take a look at boost thread, for a relatively high level threading library

Take a look at this code sample from the link:

#include <iostream>
#include <map>
#include <string>
#include <chrono>
#include <thread>
#include <mutex>

std::map<std::string, std::string> g_pages;
std::mutex g_pages_mutex;

void save_page(const std::string &url)
{
    // simulate a long page fetch
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::string result = "fake content";

    g_pages_mutex.lock();
    g_pages[url] = result;
    g_pages_mutex.unlock();
}

int main() 
{
    std::thread t1(save_page, "http://foo");
    std::thread t2(save_page, "http://bar");
    t1.join();
    t2.join();

    g_pages_mutex.lock(); // not necessary as the threads are joined, but good style
    for (const auto &pair : g_pages) {
        std::cout << pair.first << " => " << pair.second << '\n';
    }
    g_pages_mutex.unlock();
}
lolski
  • 16,231
  • 7
  • 34
  • 49
  • Thank you for the link. I'm not sure I need a mutex as I will only be accessing the data the thread is working on while the thread is shut off, and at the end of each iteration the data the thread leaves is in a perfectly fine state. Flow would be something like: start thread -> stop thread -> read data -> start thread -> stop thread -> read data and so on. – Svalorzen Dec 17 '13 at 17:38
0

I would suggest to use std::mutex and std::condition_variable to solve the problem. Here's an example how it can work with C++11:

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

using namespace std;

int main()
{
    mutex m;
    condition_variable cv;
    // Tells, if the worker should stop its work
    bool done = false;
    // Zero means, it can be filled by the worker thread.
    // Non-zero means, it can be consumed by the main thread.
    int result = 0;

    // run worker thread
    auto t = thread{ [&]{
        auto bound = 1000;
        for (;;) // ever
        {
            auto sum = 0;
            for ( auto i = 0; i != bound; ++i )
                sum += i;
            ++bound;
            auto lock = unique_lock<mutex>( m );
            // wait until we can safely write the result
            cv.wait( lock, [&]{ return result == 0; });
            // write the result
            result = sum;
            // wake up the consuming thread
            cv.notify_one();
            // exit the loop, if flag is set. This must be
            // done with mutex protection. Hence this is not
            // in the for-condition expression. 
            if ( done )
                break;
        }
    } };

    // the main threads loop
    for ( auto i = 0; i != 20; ++i )
    {
        auto r = 0;
        {
            // lock the mutex
            auto lock = unique_lock<mutex>( m );
            // wait until we can safely read the result
            cv.wait( lock, [&]{ return result != 0; } );
            // read the result
            r = result;
            // set result to zero so the worker can 
            // continue to produce new results. 
            result = 0;
            // wake up the producer
            cv.notify_one();
            // the lock is released here (the end of the scope)
        } 
        // do time consuming io at the side. 
        cout << r << endl;
    }

    // tell the worker to stop
    {
        auto lock = unique_lock<mutex>( m );
        result = 0;
        done = true;
        // again the lock is released here
    }

    // wait for the worker to finish.
    t.join();

    cout << "Finished." << endl;
}

You could do the same with std::atomics by essentially implementing spin locks. Spin locks can be slower than mutexes. So I repeat the advise on the boost website:

Do not use spinlocks unless you are certain that you understand the consequences.

I believe that mutexes and condition variables are the way to go in your case.

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120