我无法确定一个奇怪的崩溃来自哪里,但事实上它没有发生确定性使我怀疑线程。
我有这样的事情:
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
?
它看起来像我在网上看过的一些例子,但我不确定它是否绝对安全。
答案 0 :(得分:8)
我看到的唯一问题是类成员按照它们在类中声明的顺序进行初始化。由于mThread
位于所有其他类成员之前,因此线程可能在初始化之前使用它们。
要解决此问题,您可以重新安排班级成员,但我不喜欢这种方法。如果其他人出现并更改了订单,则可能会破坏代码。您应该能够让线程初始化默认值,然后在构造函数体中启动线程,因为此时所有类成员都已初始化。
答案 1 :(得分: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;
};
答案 2 :(得分:1)
是的,它可能有不良行为,因为mThread
实例尚未构建时可能会启动MyClass
。
我的拇指规则:如果我必须在构造函数中使用this
,我会做一些顽皮的事情;)。