xia*_*oyi 4 c++ multithreading c++11
我正在尝试让类运行一个线程,它将在循环中调用名为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
Dav*_*rtz 11
在完成使用之前,您不能让对象超出范围!将return 0;在年底main的原因fn,以走出去的范围.因此,当你到处打电话时tick,不能保证对象甚至不再存在.
(逻辑输入~Runnable完全被破坏.析构函数内部太晚了 - 对象已经至少部分被破坏了.)
使用继承作为线程控制的父级和实现函数的子级的方法通常是一个坏主意。这种方法的常见问题来自于构造和破坏:
如果线程是从父(控件)中的构造函数启动的,那么它可能会在构造函数完成之前开始运行,并且线程可能会在完整对象被完全构造之前调用虚函数
如果线程在父级的析构函数中停止,那么在控件加入线程时,线程正在对不再存在的对象执行方法。
在您的特定情况下,您遇到了第二种情况。程序开始执行,并在main第二个线程中启动。在这一点上,主线程和新启动的线程之间存在竞争,如果新线程更快(不太可能,因为启动线程是一项昂贵的操作),它将调用Tick将被分派到最终覆盖程序的成员方法Fn::Tick。
但是,如果主线程是快将退出的范围main,它将启动对象的破坏,它会完成的破坏Fn对象和施工过程中Runnable它会join线程。如果主线程是速度不够快,这将使它的join第二个线程之前,等待那里的第二个线程调用Tick的现在最终置换器是Runnable::Tick。请注意,这是Undefined Behavior,不能保证,因为第二个线程正在访问正在销毁的对象。
此外,还有其他可能的顺序,例如,第二个线程可以Fn::Tick在主线程开始销毁之前调度到,但在主线程销毁Fn子对象之前可能无法完成该函数,在这种情况下,您的第二个线程将调用死对象上的成员函数。
您应该遵循 C++ 标准中的方法:将控制与逻辑分离,完全构造将运行的对象并在构造期间将其传递给线程。请注意,这是 Java 的情况Runnable,建议不要扩展Thread类。请注意,从设计的角度来看,这种分离是有道理的:线程对象管理执行,而runnable是要执行的代码。线程不是股票代码,而是控制股票代码执行的东西。而在你的代码Runnable是不是可以运行,而是一些运行 碰巧从它派生的其他对象。