有一个安全的方法让std :: thread作为类的成员吗?

raf*_*lak 14 c++ multithreading initialization class c++11

我想使用一个管理线程(或几个线程)的类.使用组合,这看起来像:

class MyClass{
private:
    std::thread mythread;
    void _ThreadMain();
public:
    MyClass();
    // other fields
}
Run Code Online (Sandbox Code Playgroud)

因为a的默认构造函数std::thread是没有意义的,我需要在MyClass构造函数中显式调用它:

MyClass::MyClass() : mythread(&MyClass::_ThreadMain,this) {}
Run Code Online (Sandbox Code Playgroud)

但是,在这种情况下,该_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.
}
Run Code Online (Sandbox Code Playgroud)

这将启动该线程.在这种情况下,它将在构造类之后调用,这确实是安全的.

但是,我想知道是否有任何合理的解决方案可以让我不使用指针.感觉它应该是可能的某种方式(嘿,必须有一种方法来在构建一个类时启动一个线程!),但我无法想出任何不会引起麻烦的事情.

我考虑过使用一个条件变量,_ThreadMain等待构造函数完成它的工作,但是在构造类之前我不能使用它,对吧?(如果MyClass是派生类,这也没有用)

Nem*_*emo 10

一般来说,没有比拥有单独Start功能更好的方法了.

假设MyClass是某个未来(未知)类的基类Derived.如果线程在MyClass构造函数运行时(或之前)启动,则它总是有可能调用被覆盖的某个虚函数的"错误"实现Derived.

避免这种情况的唯一方法是让线程等到Derived完全构造完成之后,唯一的方法是在Derived构造函数完成后调用其他函数告诉线程"go"...这意味着你必须有一些单独调用的Go函数.

你可能只需要一个单独调用的Start函数,而放弃等待的复杂性.

[更新]

请注意,对于复杂的类,"两阶段构造"是一些人推荐的习语.启动线程将无缝地适应"初始化"阶段.


nos*_*sid 7

您可以将线程与移动语义结合使用:

class MyClass final
{
private:
    std::thread mythread;
    void _ThreadMain();
public:
    MyClass()
        : mythread{} // default constructor
    {
        // move assignment
        mythread = std::thread{&MyClass::_ThreadMain, this};
    }
};
Run Code Online (Sandbox Code Playgroud)

移动赋值运算符记录在下一页.特别是,它noexcept并没有创建新的线程.

  • 如果有一个vtable指针,这个解决方案可能会遇到麻烦!vtable指针将在构造函数的块之后初始化*! (3认同)
  • 你们有所有这些东西的模板,不是吗?在看到你的评论之前,我一直在考虑将评论作为答案发布,但是缩回了... (2认同)
  • @Klaus:在类`T`的构造函数体中,动态类型是`T`.所以`final`确实对安全有很大帮助,但代价是通过覆盖成员函数来防止定制.我认为这个解决方案不是很一般. (2认同)