【问题标题】:Can initialising a thread in a class constructor lead to a crash?在类构造函数中初始化线程会导致崩溃吗?
【发布时间】:2015-11-06 17:03:42
【问题描述】:

我无法确定奇怪的崩溃来自何处,但它没有确定性地发生的事实让我怀疑线程。

我有这样的事情:

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;
};

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

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

【问题讨论】:

    标签: c++ multithreading stl


    【解决方案1】:

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

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

    【讨论】:

    • 我没有意识到mutexcondition_variable 需要 初始化?或者你是说我应该明确地这样做,例如:MyClass() : mExit(false), mMutex(), mCondition(), mThread(&amp;MyClass::ThreadMain,this)
    • @Mr.Boy 我永远不会使用未初始化的变量,所以是的,我会默认在构造函数中初始化它们。
    • @NathanOliver 在初始化列表中不包括对象将导致它们被默认初始化。将它们包含在初始化列表中,将零参数传递给构造函数,将导致对象被值初始化
    • @NathanOliver 但是成员已经初始化,只是没有明确地初始化。在这种情况下,显式初始化它们可能会提示问题,但只是可能,而且总的来说,我觉得显式默认初始化非基元是浪费空间。
    • 我想我可以把它作为我问题的原因......不是在初始化列表中初始化线程,而是在 ctor 正文中解决了我的问题。不确定是要回答还是要求@NathanOliver 将其添加到他的回答中作为替代方案并接受?但简短的回答是:是的
    【解决方案2】:

    除了@NathanOliver 描述的 member-construction-order-vs-thread-early-execution 问题之外,我想指出的是,当使用虚函数代替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;
    };
    

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

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

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

    struct ThreadHandler {
        ThreadHandler() { d.start(); }
        Derived d;
    };
    

    【讨论】:

      【解决方案3】:

      是的,它可能有不良行为,因为 mThread 可能在 MyClass 实例尚未构造时启动。

      我的经验法则:如果我必须在构造函数中使用this,我会做一些顽皮的事情;)。

      【讨论】:

        猜你喜欢
        • 2021-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-17
        • 1970-01-01
        相关资源
        最近更新 更多