我想使用一个管理线程(或多个线程)的类。使用合成,这看起来像:
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
是派生类,这也没有用)
答案 0 :(得分:9)
一般而言,没有比单独使用Start
函数更好的方法了。
假设MyClass
是未来(未知)类Derived
的基类。如果线程在MyClass
构造函数运行的同时(或之前)启动,则它总是有可能调用由Derived
覆盖的某个虚函数的“错误”实现。
避免这种情况的唯一方法是让线程等到完全构造Derived
之后,唯一的方法就是在Derived
构造函数完成后调用其他函数来告诉它“go”的线程...这意味着你必须有一些单独调用的Go
函数。
您可能只需要一个单独调用的Start
函数,而放弃等待的复杂性。
[更新]
请注意,对于复杂的类,“两阶段构造”是一些人推荐的习语。启动线程将无缝地适应“初始化”阶段。
答案 1 :(得分:7)
您可以将线程与移动语义结合使用:
class MyClass final
{
private:
std::thread mythread;
void _ThreadMain();
public:
MyClass()
: mythread{} // default constructor
{
// move assignment
mythread = std::thread{&MyClass::_ThreadMain, this};
}
};
移动赋值运算符记录在下一页。特别是,它是noexcept
并且没有创建新线程。
答案 2 :(得分:1)
考虑将任务与线程管理分离并启动。
一个类创建一个运行器,任何同步原语都是类似的,另一个类启动它。这允许在线程开始之前构造runnable失败。
这也意味着runnable在运行之前是完全构造的。
现在第一次传球让跑步者成为std::thread
,但是一些帮助中止,清理和延续的东西可能很有用。
run对象可以是一个简单的可调用对象,或者可以为runnable添加额外的支持以与它进行交互。