2015-11-06 106 views
3

我还没有能够确定哪里发生了一次奇怪的事故,但事实并非如此确定性地发生,这让我怀疑是否有穿线。初始化类构造函数中的线程是否会导致崩溃?

我有这样的事情:

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

它看起来像我在网上看到的一些例子,但我不能肯定地说它绝对安全与否。

回答

7

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

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

+0

我没有意识到'mutex'和'condition_variable' _needed_ initialising?或者你是否说我应该明确这样做,例如:MyClass():mExit(false),mMutex(),mCondition(),mThread(&MyClass :: ThreadMain,this)' –

+1

@ Mr.Boy我永远不会使用未初始化的变量,所以是的,我会默认初始化它们在构造函数。 – NathanOliver

+0

@NathanOliver在初始化列表中不包含_objects_将导致它们为_default initialized_。将它们包含在初始化程序列表中,将零参数传递给构造函数将导致对象被_value initialized_。 – Snps

1

是的,它可能会有不好的行为,因为mThread可能会启动,而MyClass实例尚未构建。

我的拇指规则:如果我必须在构造函数中使用this,我会做一些调皮的事情)。

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