初始化类构造函数中的线程会导致崩溃吗?

Mr.*_*Boy 3 c++ multithreading stl

我无法确定一个奇怪的崩溃来自哪里,但它没有确定性地发生这一事实让我怀疑是线程.

我有这样的事情:

class MyClass
{
 MyClass() : mExit(false), mThread(&MyClass::ThreadMain,this)
 {}

 void ThreadMain()
 {
  unique_lock<mutex> lock(mMutex);
  mCondition.wait(lock, [&] { return mExit; });
 }

 std::thread mThread;
 std::mutex mMutex;
 std::condition_variable mCondition;
 bool mExit;
};
Run Code Online (Sandbox Code Playgroud)

显然这是非常简化但我不确定崩溃发生在哪里,所以我想问这个设置是否导致问题?例如,初始化的所有内容是什么顺序 - 是否有可能ThreadMain在类的实例完全构造之前运行?

它看起来像我在网上看到的一些例子,但我不确定它是否绝对安全.

Nat*_*ica 8

我看到的唯一问题是类成员按照它们在类中声明的顺序进行初始化.由于mThread在所有其他类成员之前,线程可能在它们被初始化之前使用它们.

要解决此问题,您可以重新安排班级成员,但我不喜欢这种方法.如果其他人出现并更改了订单,则可能会破坏代码.您应该能够让线程获得默认初始化,然后在构造函数体中启动线程,因为此时所有类成员都已初始化.


Snp*_*nps 5

除了@NathanOliver描述的 member-construction-order-vs-thread-early-execution 问题之外,我想指出,当使用 virtual 函数代替 时,代码仍然会表现出未定义的行为ThreadMain

在设计中使用虚函数是一个问题,因为虚函数是从 vtable 中查找的,并且指向 vtable 的指针在构造函数块执行完毕之前不会初始化。因此,您最终会得到一个线程,该线程使用指向尚未初始化的函数(即 UB)的指针。

此类 RAII 线程处理程序问题的一般解决方案是将对象的初始化与线程的执行分开,例如使用start函数。这也将消除对成员构造顺序的依赖。

struct MyClass {
    MyClass() : mExit(false) {}
    void start() { mThread = std::thread{&ThreadMain, this}; } // Start function.
    virtual void ThreadMain() = 0;

    std::atomic<bool> mExit; // Not even bool is atomic :)
    std::mutex mMutex;
    std::condition_variable mCondition;
    std::thread mThread;
};
Run Code Online (Sandbox Code Playgroud)

这确保了MyClass在启动线程时构造。现在,也可以使用多态性。

struct Derived : public MyClass {
    virtual void ThreadMain() {
        std::unique_lock<std::mutex> lock(mMutex);
        mCondition.wait(lock, [&] { return mExit.load(); });
    }
};
Run Code Online (Sandbox Code Playgroud)

然而,现在必须使用两个语句而不是一个语句来启动线程,例如, MyClass m; m.start();。为了解决这个问题,我们可以简单地创建一个包装类来执行start构造函数主体中的函数。

struct ThreadHandler {
    ThreadHandler() { d.start(); }
    Derived d;
};
Run Code Online (Sandbox Code Playgroud)