Archive for April, 2015

In C++, even a thread is not thread-safe (or: why you should use tasks part 2)

April 12, 2015

 

You are reading the old blog! This post has been moved to http://www.ohadsoft.com/2015/04/in-c-even-a-thread-is-not-thread-safe-or-why-you-should-use-tasks-part-2/

 

Consider the following (contrived) code:

using namespace std;

void work()
{
    this_thread::sleep_for(chrono::seconds(2));
}

void wait(thread* t)
{
    t->join();
}

int main()
{
    thread worker(work);
    thread waiter1(wait, &worker);
    thread waiter2(wait, &worker);

    waiter1.join();
    waiter2.join();

    return 0;
}

The intent of the code is quite clear – we spawn a worker thread and have two threads wait for it to finish. Let’s assume that the sleep is long enough so that both threads try and join while the worker is still running (otherwise the thread stops being joinable and calling join throws). Even under that assumption, the code’s behavior is not defined (UB). The reason is in the docs:

Note that any operations on the thread object itself are not synchronized (unlike the operations within the thread it represents).

In other words, the std::thread object itself is not thread safe, so we can’t call its methods concurrently (which is what we’re doing in waiter1 and waiter2). We’ll have to do something like this:

using namespace std;

mutex m;

void work()
{
     this_thread::sleep_for(chrono::seconds(2));
}

void wait(thread* t)
{
    lock_guard<std::mutex> lock(m);
    if (t->joinable())
    {
        t->join();
    }
}

int _tmain()
{
    thread worker(work);
    thread waiter1(wait, &worker);
    thread waiter2(wait, &worker);

    waiter1.join();
    waiter2.join();

    return 0;
}

Of course, were you using tasks, you wouldn’t have needed to concern yourself with such trivialities:

using namespace std;
using namespace concurrency;

auto worker = create_task([]
{
    this_thread::sleep_for(chrono::seconds(2));
});

auto waiter1 = create_task([&worker]
{
    worker.wait();
});

auto waiter2 = create_task([&worker]
{
     worker.wait();
});

waiter1.wait(); //works
waiter2.wait(); //great

I hope this has taken you one step closer to ditching bare threads (if you haven’t already). If it didn’t, be sure to check out (Not) using std::thread by Andrzej Krzemieński (his blog is great all around, so I recommend you check it out anyway).

Reminder: