I would like to use a class that manages a thread (or several threads). Using composition, this would look like:
class MyClass{
private:
std::thread mythread;
void _ThreadMain();
public:
MyClass();
// other fields
}
Because the default constructor for an std::thread
is pointless, I would need to call it explicitly in MyClass
constructor:
MyClass::MyClass() : mythread(&MyClass::_ThreadMain,this) {}
However, in this case the _ThreadMain
method will be likely executed before MyClass
is constructed, leading to any kind of weird behaviour. This is clearly unsafe. How can I fix this?
An obvious solution would be to use a pointer to std::thread
instead, and add another member function:
void MyClass::Start(){
// This time mythread is of type std::thread*
mythread = new std::thread(&MyClass::_ThreadMain,this); // One could use std::unique_pointer instead.
}
which would fire up that thread. In this case it would be called after the class is constructed, which will be indeed safe.
However, I am wondering if there is any reasonable solution that would allow me not to use pointers for this. It feels like it should be possible somehow (hey, there must be a way to launch a thread when a class is constructed!), but I cannot come up with anything that would not cause troubles.
I have considered using a conditional variable so that the _ThreadMain
waits till the constructor has done its work, but I cannot use one before the class is constructed, right? (This would also be unhelpful if MyClass
was a derived class)
join() cannot be called on that thread object any more, since it is no longer associated with a thread of execution. It is considered an error to destroy a C++ thread object while it is still "joinable".
If you don't join these threads, you might end up using more resources than there are concurrent tasks, making it harder to measure the load. To be clear, if you don't call join , the thread will complete at some point anyway, it won't leak or anything. But this some point is non-deterministic.
A thread object is said to be joinable if it identifies/represent an active thread of execution. A thread is not joinable if: It was default-constructed. If either of its member join or detach has been called.
std::thread::operator= thread objects cannot be copied (2).
You can use a thread in combination with move semantics:
class MyClass final
{
private:
std::thread mythread;
void _ThreadMain();
public:
MyClass()
: mythread{} // default constructor
{
// move assignment
mythread = std::thread{&MyClass::_ThreadMain, this};
}
};
The move assignment operator is documented on the following page. In particular, it is noexcept
and no new thread is created.
There is no better way, in general, than having a separate Start
function.
Suppose MyClass
is a base class for some future (unknown) class Derived
. If the thread is started while (or before) the MyClass
constructor runs, it always risks calling the "wrong" implementation of some virtual function overridden by Derived
.
The only way to avoid this is to have the thread wait until after Derived
is fully constructed, and the only way to do that is to call some other function after the Derived
constructor completes to tell the thread to "go"... Which means you must have some kind of separately-invoked Go
function.
You might as well just have a separately-invoked Start
function instead and forego the complexity of waiting.
[Update]
Note that, for complex classes, "Two-Phase Construction" is an idiom recommended by some. Starting the thread would fit seamlessly into the "initialization" phase.
Consider separating the task from the thread management and launching.
One class creates a runner and any synchronization primitives snd the like, The other handles launching it. This allows construction of the runnable to fail before threading starts.
It also means the runnable is fully constructed prior to it being run.
Now a first pass would have the runner be a std::thread
, but some stuff helping with abort and cleanup and continuations can be useful.
The run object could be a simple callable, or could add extra supportmfor the runnable to interact with it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With