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

时间:2014-05-11 15:09:30

标签: c++ multithreading class c++11 initialization

我想使用一个管理线程(或多个线程)的类。使用合成,这看起来像:

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是派生类,这也没有用)

3 个答案:

答案 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添加额外的支持以与它进行交互。