我正在尝试让类运行一个线程,它将在循环中调用名为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
答案 0 :(得分:11)
在完成使用之前,您不能让对象超出范围! return 0;
末尾的main
会导致fn
超出范围。因此,当您开始调用tick
时,无法保证对象甚至不再存在。
(~Runnable
中的逻辑完全被破坏。析构函数内部太迟了 - 对象已经至少部分被破坏了。)
答案 1 :(得分:4)
对于线程使用继承与父服务作为控制的方法以及子实现函数的方法通常是个坏主意。这种方法的常见问题来自构建和破坏:
如果线程从父(控件)中的构造函数启动,则它可能在构造函数完成之前开始运行,并且线程可能在完整构造完整对象之前调用虚函数
< / LI>如果线程在父进程的析构函数中停止,那么在控件加入线程时,线程正在对不再存在的对象执行方法。
在您的特定情况下,您将遇到第二种情况。程序开始执行,并在main
中启动第二个线程。此时主线程和新启动之间存在竞争,如果新线程更快(不太可能,因为启动线程是一项昂贵的操作),它将调用将被调度的成员方法Tick
到最后的覆盖者Fn::Tick
。
但是如果主线程更快,它将退出main
的范围,它将开始销毁对象,它将完成对Fn
对象的破坏以及构造Runnable
1}}它将join
线程。如果主线程足够快,它将在第二个线程之前进入join
并等待第二个线程在 now 最终覆盖上调用Tick
是Runnable::Tick
。请注意,这是 Undefined Behavior ,并且无法保证,因为第二个线程正在访问正在销毁的对象。
此外,还有其他可能的排序,例如,第二个线程可以在主线程开始销毁之前调度到Fn::Tick
,但在主线程销毁Fn
之前可能无法完成该功能子对象,在这种情况下,你的第二个线程将在死对象上调用成员函数。
您应该遵循C ++标准中的方法:将控件与逻辑分开,完全构造将运行的对象并将其传递给线程施工。请注意,这是Java Runnable
的情况,建议不要扩展Thread
类。请注意,从设计的角度来看,这种分离是有意义的:线程对象管理执行,而 runnable 是要执行的代码。
线程不是股票代码,而是控制股票代码的执行。并且在您的代码中Runnable
不是可以运行的东西,而是运行恰好从其衍生的其他对象。