我想使用一个管理一个线程(或多个线程)的类。使用组合,这看起来像:
class MyClass{
private:
std::thread mythread;
void _ThreadMain();
public:
MyClass();
// other fields
}
因为
std::thread
的默认构造函数毫无意义,我需要在 MyClass
构造函数中显式调用它:
MyClass::MyClass() : mythread(&MyClass::_ThreadMain,this) {}
但是,在这种情况下,
_ThreadMain
方法可能会在MyClass
构建之前执行,从而导致任何奇怪的行为。这显然是不安全的。我该如何解决这个问题?
一个明显的解决方案是使用指向
std::thread
的指针,并添加另一个成员函数:
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.
}
这会启动该线程。在这种情况下,它将在类构造完成后调用,这确实是安全的。
但是,我想知道是否有任何合理的解决方案可以让我不使用指针。感觉这应该是可能的(嘿,在构造类时必须有一种方法来启动线程!),但我无法想出任何不会引起麻烦的东西。
我考虑过使用条件变量,以便
_ThreadMain
等待构造函数完成其工作,但我不能在构造类之前使用条件变量,对吧? (如果 MyClass
是派生类,这也没有帮助)
您可以将线程与 移动语义结合使用:
class MyClass final
{
private:
std::thread mythread;
void _ThreadMain();
public:
MyClass()
: mythread{} // default constructor
{
// move assignment
mythread = std::thread{&MyClass::_ThreadMain, this};
}
};
移动赋值运算符记录在下页中。特别是,它是
noexcept
并且没有创建新线程。
一般来说,没有比拥有单独的
Start
函数更好的方法了。
假设
MyClass
是某个未来(未知)类 Derived
的基类。如果线程在 MyClass
构造函数运行时(或之前)启动,它总是有调用被 Derived
覆盖的某些虚拟函数的“错误”实现的风险。
避免这种情况的唯一方法是让线程等待,直到
Derived
完全构造完毕,而唯一的方法是在 Derived
构造函数完成后调用其他函数来告诉线程“开始” ...这意味着您必须具有某种单独调用的 Go
函数。
您不妨只使用单独调用的
Start
函数,而放弃等待的复杂性。
[更新]
请注意,对于复杂的类,“两阶段构造”是一些人推荐的习惯用法。启动线程将无缝地进入“初始化”阶段。
考虑将任务与线程管理和启动分开。
一个类创建一个运行器和任何同步原语等,另一个类负责启动它。这使得可运行的构造在线程开始之前失败。
这也意味着可运行对象在运行之前已完全构建。
现在,第一遍将使跑步者成为
std::thread
,但一些有助于中止、清理和继续的东西可能会很有用。
运行对象可以是一个简单的可调用对象,或者可以为可运行对象添加额外的支持以与其交互。
没有必要滥用指针。有两件事需要注意:
std::thread
有一个移动赋值运算符,我们可以用它来在初始化后启动它std::thread
需要正确分离或加入(接受的答案失败)class MyClass {
private:
std::thread mythread;
void ThreadMain(); // note: names like _ThreadMain are reserved; remove the _
public:
MyClass() {
// can't init mythread in member initializer list because it might result
// in _ThreadMain() using this object before it's fully initialized
mythread = std::thread(&MyClass::ThreadMain, this);
}
~MyClass() {
mythread.join(); // remember to join here, or to detach in the default constructor
}
// other fields ...
}
请注意,
ThreadMain()
可以在MyClass
完全初始化之前执行。然而,这并不重要,因为此时它的所有子对象都已初始化。
只有当 MyClass
是多态类时才会出现问题,在这种情况下,您应该创建一个单独的 Start()
成员函数。
注意:如果您使用的是 C++20,则可以避免编写析构函数,只需使用
std::jthread
代替 std::thread
。