How to invoke member function safely when inheriting std::thread

My code is as below:

  class MyThread : public std::thread {
        int a_;

    public:
        MyThread(int a)
            : std::thread(&MyThread::run, this),
              a_(a)
        { }

        void run() {
            // use a_
        }
    };

I want to have my own thread class which has all the methods that std::thread provides, so I let MyThread class inherit std::thread. In MyThread's constructor, I pass its member function to std::thread. The compilation is OK, but I am concerned it there a race condition between invoking run() in std::thread's construtor and initializing a_.

Is there a way to make it safe?

3 answers

  • answered 2019-10-08 03:42 ALX23z

    To fix the error you can write:

        MyThread(int a)
            :a_(a)
        { 
            (std::thread&)(*this) = std::thread(&MyThread::run, this);
         }
    

    This way, the thread is run with initialized a_.

    In general, I don't think it is a good idea to inherit from std::thread. Better make it a private member and run it. Otherwise user can do weird shit if you allow them to cast your class to std::thread& publicly. Like executing a different function than what you intended.

  • answered 2019-10-08 03:55 selbie

    Don't do it that way. "Has-a" (composition) has a lot of advantages over "is-a" (inheritance).

    class MyThread
    {
        std::thread _thread;
        int _a;
    public:
    
        MyThread(int a) : _a(a)
        {
            _thread = std::thread([this] {run();});
        }
    
        void run()
        {
           // thread code here
        };
    
        void join()
        {
            _thread.join();
        }
    };
    

    A better approach would be to recognize that the thread and the operation on that thread are two distinct objects:

    class WorkerOperation
    {
        int _a;
    public:
       WorkerOperation(int a) :  _a(a)
       {
       }
    
       void run()
       {
         // your code goes here
       }
    };
    

    And then to create a thread:

    shared_ptr<WorkerOperation> spOp = make_shared<WorkerOperation>(42);
    std::thread t = std::thread([spOp] {spOp->run();});
    

    And if you really need to pair up the operation and the thread:

    std::pair<WorkerOperation, std::thread> threadpair;
    threadpair.first = spOp;
    threadpair.second = std::move(t);
    

  • answered 2019-10-08 13:07 Jarod42

    but I am concerned it there a race condition between invoking run() in std::thread's construtor and initializing a_.

    Yes,

    You might reorder member/base:

    struct MyThreadData
    {
        int _a;
    };
    
    struct MyThread : MyThreadData, std::thread
    {
    public:
       explicit MyThread(int a) : MyThreadData{a}, _thread([this] {run();}) {}
    
        void run()
        {
           // thread code here
        };
    };