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

问题描述 投票:0回答:4

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

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

c++ multithreading class c++11 initialization
4个回答
16
投票

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

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

移动赋值运算符记录在下页中。特别是,它是

noexcept
并且没有创建新线程。


10
投票

一般来说,没有比拥有单独的

Start
函数更好的方法了。

假设

MyClass
是某个未来(未知)类
Derived
的基类。如果线程在
MyClass
构造函数运行时(或之前)启动,它总是有调用被
Derived
覆盖的某些虚拟函数的“错误”实现的风险。

避免这种情况的唯一方法是让线程等待,直到

Derived
完全构造完毕,而唯一的方法是在
Derived
构造函数完成后调用其他函数来告诉线程“开始” ...这意味着您必须具有某种单独调用的
Go
函数。

您不妨只使用单独调用的

Start
函数,而放弃等待的复杂性。

[更新]

请注意,对于复杂的类,“两阶段构造”是一些人推荐的习惯用法。启动线程将无缝地进入“初始化”阶段。


1
投票

考虑将任务与线程管理和启动分开。

一个类创建一个运行器和任何同步原语等,另一个类负责启动它。这使得可运行的构造在线程开始之前失败。

这也意味着可运行对象在运行之前已完全构建。

现在,第一遍将使跑步者成为

std::thread
,但一些有助于中止、清理和继续的东西可能会很有用。

运行对象可以是一个简单的可调用对象,或者可以为可运行对象添加额外的支持以与其交互。


0
投票

没有必要滥用指针。有两件事需要注意:

  • 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

© www.soinside.com 2019 - 2024. All rights reserved.