C++11: std::threads managed by a designated class

来源:互联网 发布:2015-16西部决赛数据 编辑:程序博客网 时间:2024/06/05 19:51

Recently I have noticed an unobvious problem that may appear when using std::threads as class fields. I believe it is more than likely to meet if one is not careful enough when implementing C++ classes, due to it’s tricky nature. Also, its solution provides an elegant example of what has to be considered when working with threads in object-oriented C++, therefore I decided to share it.

Consider a scenario where we would like to implement a class that represents a particular thread activity. We would like it to:

  • start a new thread it manages when an instance is constructed
  • stop it when it is destructed

I will present the obvious implementation, explain the problem with it, and describe how to deal with it.

So the outline would certainly look like this:

1
2
3
4
5
6
7
8
9
10
#include <thread>
classMyClass{
public:
    MyClass();
    ~MyClass();
private:
    std::threadthe_thread;
    boolstop_thread = false;// Super simple thread stopping.
    voidThreadMain();
};

Okay, but the default std::thread constructor is pretty pointless here, it “Creates new thread object which does not represent a thread“. So a pretty obvious solution is to explicitly use another constructor, which will actually launch the thread:

1
the_thread(&MyClass::ThreadMain,this)

Then we can implement the thread routines within ThreadMain method.  The destructor would take care of stopping the thread gracefully and waiting for it to terminate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <thread>
#include <chrono>
classMyClass{
public:
    MyClass() : the_thread(&MyClass::ThreadMain, this) {}
    ~MyClass(){
         stop_thread = true;
         the_thread.join();
    }
private:
    std::threadthe_thread;
    boolstop_thread = false;// Super simple thread stopping.
    voidThreadMain(){
        while(!stop_thread){
            // Do something useful, e.g:
            std::this_thread::sleep_for( std::chrono::seconds(1) );
        }
    }
};

This seems like an elegantly implemented class managing the thread. It will even [seem to] work correctly!

But there is a critical problem with it.

The new thread will start running as soon as it is constructed. It will not wait for the constructor to finish its work (why should it?). It may, and sometimes, at random, it will, leading you into a false sense of security and correctness. It will not even wait for other class fields to be constructed. It has the right to cause crash when accessing stop_thread. And what if your class has other fields that are not atomic? The thread can start using uninitialized objects, problems guaranteed.

One possible solution would be to have the constructor notify the thread when it is safe to start, from within its body. This might be accomplished with an atomic or with a conditional variable. Keep in mind that it would need to be constructed before the std::thread, so that it is ready to use when the thread starts (so the order of construction really does matter!).

This might work in case of the code I use for demonstration. But in general case this will do no good. Imagine use a hierarchy of polymorphic thread-managing classes; imagine that ThreadMain is virtual. In such scenario its addresses is derived from the vtable. But the vtable pointer is initialized after the block of the constructor! This means the starting thread would start from calling a wrong function, leading to a variety of confusing behavior. The idea of notifying it when to start won’t help here.

A universal solution would be to prepare then class in two steps.

  • First, construct it.
  • When it is ready to use, call Start() which will actually launch the thread.

Like this:

1
2
MyClass instance;
instance.Start();

So we will need to start with no thread running, and construct it dynamically when Start is called. My first thought was:

1
2
3
4
5
6
7
8
9
10
11
classMyClass{
public:
    MyClass(){}
    ~MyClass(){/****/}
    voidStart(){
        the_thread = newstd::thread(&MyClass::ThreadMain,this);
    }
private:
    std::thread* the_thread; // Or you could use std::unique_ptr<>.
    /****/
};

 

But this just feels wrong. Pointers? Seriously, are we forced to manually manage memory?

No. std::thread is movable! So we can use that boring default std::thread constructor (not that pointless, right?) to construct it at the beginning, and then, when Start() is called, we will substitute it with an actual running thread! Like that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
classMyClass{
public:
    /* Explicitly using the default constructor to
     * underline the fact that it does get called */
    MyClass() : the_thread() {}
    ~MyClass(){
        stop_thread = true;
        if(the_thread.joinable()) the_thread.join();
    }
    voidStart(){
        // This will start the thread. Notice move semantics!
        the_thread = std::thread(&MyClass::ThreadMain,this);
    }
private:
    std::threadthe_thread;
    /****/
};

Now this is both safe and elegant.

This scenario has taught me to stay vigilant when mixing asynchronous execution with stuff that C++ does on the lower levels. I do hope that it has opened your eyes too, or that at least you will remember it as a simple yet interesting case!


"stop_thread" is not thread safe. We should consider using “std::atomic_bool stop_thread";

https://rafalcieslak.wordpress.com/2014/05/16/c11-stdthreads-managed-by-a-designated-class/

0 0