5

クラスにスレッドを実行させようとしています。スレッドは、ループで 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
4

2 に答える 2

12

オブジェクトの使用が完了するまで、オブジェクトをスコープ外にすることはできません。return 0;最後には範囲外になりますmainfnしたがって、 を呼び出すtickまでに、オブジェクトが存在しなくなるという保証はありません。

(ロジック~Runnableが完全に壊れています。デストラクタ内では遅すぎます。オブジェクトは少なくとも部分的に破棄されています。)

于 2012-05-17T11:15:32.963 に答える
6

親がスレッドのコントロールとして機能し、子が関数を実装する継承を使用するアプローチは、一般的には悪い考えです。このアプローチの一般的な問題は、構築と破棄に起因します。

  • スレッドが親 (コントロール) のコンストラクターから開始された場合、コンストラクターが完了する前にスレッドが実行を開始し、完全なオブジェクトが完全に構築される前にスレッドが仮想関数を呼び出す可能性があります。

  • スレッドが親のデストラクタで停止した場合、コントロールがスレッドに参加するまでに、スレッドは存在しないオブジェクトに対してメソッドを実行しています。

あなたの特定のケースでは、2番目のケースにぶつかっています。プログラムの実行が開始されmain、2 番目のスレッドが開始されます。その時点で、メイン スレッドと新しく起動されたスレッドの間で競合が発生します。新しいスレッドの方が高速な場合 (スレッドの起動はコストのかかる操作であるため、可能性は低いです)、Tick最終的な overrider にディスパッチされるメンバー メソッドが呼び出されますFn::Tick

しかし、メイン スレッドがより高速な場合、メイン スレッドは のスコープを終了しmain、オブジェクトの破棄を開始し、オブジェクトの破棄を完了し、スレッドFnの構築中にRunnableそれをjoin実行します。メイン スレッドが十分に高速な場合は、2 番目のスレッドの前に到達し、2 番目のjoinスレッド. これはUndefined Behaviorであり、保証されていないことに注意してください。これは、2 番目のスレッドが破棄中のオブジェクトにアクセスしているためです。TickRunnable::Tick

また、他の可能な順序があります。たとえば、2 番目のスレッドFn::Tickはメイン スレッドが破棄を開始する前にディスパッチできますが、メイン スレッドがFnサブ オブジェクトを破棄する前に関数を完了しない可能性があります。この場合、2 番目のスレッドは死んだオブジェクトのメンバー関数。

むしろ、C++ 標準のアプローチに従う必要があります。コントロールロジックから分離し、実行されるオブジェクトを完全に構築し、構築中にスレッドに渡します。これは、クラスRunnableを拡張するよりも推奨されるJava の の場合であることに注意してください。Thread設計の観点からは、この分離は理にかなっていることに注意してください。スレッドオブジェクトが実行を管理し、実行可能なオブジェクトが実行するコードです。スレッドはティッカーではなく、ティッカー実行を制御するものです。そして、あなたのコードは実行できるものではなく、実行されるものですRunnableそこからたまたま派生する他のオブジェクト。

于 2012-05-17T11:34:51.973 に答える