[英]C++11 thread doesn't work with virtual member function
我正在尝试让类运行一个线程,它将在循环中调用名为Tick()的虚拟成员函数。 然后我尝试派生一个类并覆盖base :: Tick()。
但是在执行时,程序只调用基类的Tick而不是覆盖它。 任何解决方案
#include <iostream>
#include <atomic>
#include <thread>
#include <chrono>
using namespace std;
class Runnable {
public:
Runnable() : running_(ATOMIC_VAR_INIT(false)) {
}
~Runnable() {
if (running_)
thread_.join();
}
void Stop() {
if (std::atomic_exchange(&running_, false))
thread_.join();
}
void Start() {
if (!std::atomic_exchange(&running_, true)) {
thread_ = std::thread(&Runnable::Thread, this);
}
}
virtual void Tick() {
cout << "parent" << endl;
};
std::atomic<bool> running_;
private:
std::thread thread_;
static void Thread(Runnable *self) {
while(self->running_) {
self->Tick();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
};
class Fn : public Runnable {
public:
void Tick() {
cout << "children" << endl;
}
};
int main (int argc, char const* argv[])
{
Fn fn;
fn.Start();
return 0;
}
输出:
parent
在完成使用之前,您不能让对象超出范围! return 0;
在main
结束时fn
超出范围。 因此,当你开始调用tick
,不能保证对象甚至不再存在。
( ~Runnable
的逻辑完全被破坏。析构函数内部太迟了 - 对象已经至少部分被破坏了。)
使用继承与父作为线程的控件和实现函数的子项的方法通常是一个坏主意。 这种方法的常见问题来自构建和破坏:
如果线程是从父(控件)中的构造函数启动的,则它可能在构造函数完成之前开始运行,并且线程可能在完整构造完整对象之前调用虚函数
如果线程在父类的析构函数中停止,那么在控件加入线程时,线程正在对不再存在的对象执行方法。
在你的特殊情况下,你正在遇到第二种情况。 程序开始执行,并在main
中启动第二个线程。 此时主线程和新启动之间存在竞争,如果新线程更快(不太可能,因为启动线程是一项昂贵的操作),它将调用将被调度到最终覆盖的成员方法Tick
Fn::Tick
。
但是如果主线程更快,它将退出main
的范围,并且它将开始破坏对象,它将完成对Fn
对象的破坏,并且在构造Runnable
期间它将join
线程。 如果主线程足够快,它将在第二个线程之前进入join
,并在那里等待第二个线程在现在最终的覆盖器上运行Tick
,这个覆盖器是Runnable::Tick
。 请注意,这是未定义的行为 ,并且无法保证,因为第二个线程正在访问正在销毁的对象。
此外,还有其他可能的排序,例如,第二个线程可以在主线程开始销毁之前调度到Fn::Tick
,但可能在主线程销毁Fn
子对象之前没有完成该函数,在这种情况下你的第二个线程将在死对象上调用成员函数。
您应该遵循C ++标准中的方法:将控件与逻辑分开,完全构造将运行的对象并在构造期间将其传递给线程。 请注意,这是Java的Runnable
的情况,建议使用它来扩展Thread
类。 请注意,从设计的角度来看,这种分离是有意义的: 线程对象管理执行,而runnable是要执行的代码。 线程不是股票代码 ,而是控制股票代码执行的内容。 在你的代码中, Runnable
不是可以运行的东西,而是运行其他碰巧从它派生的对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.