【问题标题】:Is there a safe way to have a std::thread as a member of a class?有没有一种安全的方法可以让 std::thread 作为类的成员?
【发布时间】:2014-05-11 15:09:29
【问题描述】:

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

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


【解决方案1】:

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

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

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

【讨论】:

  • 当你移动一个线程对象时,它是启动一个新的执行线程,还是真的移动它?
  • 这个解决方案可能会遇到麻烦,如果有一个vtable指针! vtable 指针将在 构造函数的块之后被初始化!
  • 你们有所有这些东西的模板,不是吗?在看到您的评论之前,我一直在考虑发布我的评论作为答案,但撤回了...
  • @Klaus:你是对的。但是,使用线程时可能会遇到各种麻烦。我现在已经完成了课程final
  • @Klaus:在类T的构造函数体中,动态类型为T。所以final 确实对安全性有很大帮助,但代价是通过覆盖成员函数来防止自定义。因此我认为这个解决方案不是很通用。
【解决方案2】:

一般来说,没有比单独的Start 函数更好的方法了。

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

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

您不妨只使用一个单独调用的Start 函数,并放弃等待的复杂性。

[更新]

请注意,对于复杂的类,“两阶段构造”是一些人推荐的成语。启动线程将无缝融入“初始化”阶段。

【讨论】:

    【解决方案3】:

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

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

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

    现在第一次通过时,跑步者应该是 std::thread,但一些有助于中止、清理和延续的东西可能会很有用。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-15
      • 1970-01-01
      • 2021-09-30
      • 2011-08-31
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      • 2012-09-15
      相关资源
      最近更新 更多