1

同期イベント処理のための複数の戦略へのインターフェースとして機能するBaseクラスがあります。ここで、イベントを非同期的に処理する戦略が必要です。コードのリファクタリングを最小限に抑えるために、各戦略には非同期イベント処理用の独自の内部スレッドがあります。私の主な関心事は、このスレッドのライフサイクルをどのように管理するかです。派生ストラテジークラスは、コードベース全体で構築および破棄されるため、ストラテジークラスの外部でスレッドのライフサイクル(開始/停止)を管理することは困難です。

私は次のコードになりました:

#include <iostream>
#include <cassert>

#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>

struct Base
{
    virtual ~Base()
    {
        std::cout << "In ~Base()" << std::endl;

        // For testing purpose: spend some time in Base dtor
        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
    }

    virtual void processEvents() = 0;

    void startThread()
    {
        if(_thread)
        {
            stopThread();
        }
        _thread.reset(new boost::thread(&Base::processEvents, this));
        assert(_thread);
    }

    void stopThread()
    {
        if(_thread)
        {
            std::cout << "Interrupting and joining thread" << std::endl;
            _thread->interrupt();
            _thread->join();
            _thread.reset();
        }
    }

    boost::shared_ptr<boost::thread> _thread;
};

struct Derived : public Base
{
    Derived()
    {
        startThread();
    }

    virtual ~Derived()
    {

        std::cout << "In ~Derived()" << std::endl;

        // For testing purpose: make sure the virtual method is called while in dtor
        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));

        stopThread();

    }

    virtual void processEvents()
    {
        try
        {
            // Process events in Derived specific way
            while(true)
            {
                // Emulated interruption point for testing purpose
                boost::this_thread::sleep(boost::posix_time::milliseconds(100));
                std::cout << "Processing events..." << std::endl;
            }
        }
        catch (boost::thread_interrupted& e)
        {
            std::cout << "Thread interrupted" << std::endl;
        }
    }
};

int main(int argc, char** argv)
{
    Base* b = new Derived;
    delete b;
    return 0;
}

ご覧のとおり、スレッドは中断され、Derivedクラスのデストラクタに参加しています。Stackoverflowに関する多くのコメントは、デストラクタのスレッドに参加することは悪い考えであると主張しています。ただし、Derivedクラスの構築/破棄を通じてスレッドのライフサイクルを管理する必要があるという制約を考慮すると、これ以上のアイデアは見つかりません。誰かがより良い提案をしていますか?

4

1 に答える 1

0

リソースの1つがスレッドであっても、クラスが破棄されたときにクラスが作成するリソースを解放することをお勧めします。ただし、デストラクタで重要なタスクを実行する場合は、時間をかけてその影響を完全に調べる価値があることがよくあります。


デストラクタ

一般的なルールは、デストラクタで例外をスローしないことです。オブジェクトDerivedが別の例外から巻き戻されているスタック上にあり、Derived::~Derived()例外をスローした場合、次にstd::terminate()呼び出され、アプリケーションを強制終了します。Derived::~Derived()は明示的に例外をスローしていませんが、呼び出している関数の一部(など)がスローされる可能性があることを考慮することが重要です_thread->join()

が目的の動作である場合std::terminate()、変更は必要ありません。ただし、std::terminate()望ましくない場合は、それをキャッチboost::thread_interruptedして抑制します。

try
{
  _thread->join();
}
catch (const boost::thread_interrupted&)
{
  /* suppressed */ 
}

継承

Base非同期動作を階層の内部に分離することにより、コードの再利用とコードのリファクタリングの最小化に継承が使用されたように見えます。ただし、ボイラープレートロジックの一部はにも含まれていDerviedます。から派生したクラスBaseはすでに変更する必要があるため、これらのクラス内の定型ロジックとコードの量を最小限に抑えるために、集約またはCRTPを検討することをお勧めします。

たとえば、ヘルパータイプを導入して、スレッドロジックをカプセル化できます。

class AsyncJob
{
public:
  typedef boost::function<void()> fn_type;

  // Start running a job asynchronously.
  template <typename Fn>
  AsyncJob(const Fn& fn)
    : thread_(&AsyncJob::run, fn_type(fn))
  {}

  // Stop the job.
  ~AsyncJob()
  {
    thread_.interrupt();

    // Join may throw, so catch and suppress.
    try { thread_.join(); }
    catch (const boost::thread_interrupted&) {}
  }

private: 

  //  into the run function so that the loop logic does not
  // need to be duplicated.
  static void run(fn_type fn)
  {
    // Continuously call the provided function until an interrupt occurs.
    try
    {
      while (true)
      {
        fn();

        // Force an interruption point into the loop, as the user provided
        // function may never call a Boost.Thread interruption point.
        boost::this_thread::interruption_point();
      }
    }
    catch (const boost::thread_interrupted&) {}
  }

  boost::thread thread_;
};

Derivedこのヘルパークラスは、のコンストラクターで集約および初期化できます。ボイラープレートコードの多くが不要になり、他の場所で再利用できます。

struct Derived : public Base
{
    Derived()
      : job_(boost::bind(&Base::processEvents, this))
    {}

    virtual void processEvents()
    {
      // Process events in Derived specific way
    }

private:

  AsyncJob job_;
};

もう1つの重要なポイントはAsyncJob、Boost.Thread割り込みポイントをループロジックに強制することです。ジョブシャットダウンロジックは、割り込みポイントの観点から実装されています。したがって、反復中に中断ポイントに到達することが重要です。そうしないと、ユーザーコードが中断ポイントに到達しない場合に、デッドロックが発生する可能性があります。


寿命

オブジェクトの存続期間に関連付ける必要があるのはスレッドの存続期間であるかどうか、またはオブジェクトの存続期間に関連付ける必要があるのは非同期イベント処理であるかどうかを調べます。後者の場合は、スレッドプールの使用を検討する価値があるかもしれません。スレッドプールは、最大制限を課すなど、スレッドリソースをより細かく制御できるほか、何もしないスレッドや短命のスレッドの作成/破棄に費やす時間など、無駄なスレッドの量を最小限に抑えることができます。

たとえば、ユーザーが500Derviedクラスの配列を作成する場合を考えてみます。500の戦略を処理するために500のスレッドが必要ですか?または、25のスレッドで500の戦略を処理できますか?一部のシステムでは、スレッドの作成/破棄にコストがかかる可能性があり、OSによって最大スレッド制限が課される場合があることに注意してください。


結論として、トレードオフを調べて、どの動作が許容できるかを判断します。特にコードベースのさまざまな領域に影響を与えるスレッドモデルを変更する場合は、コードのリファクタリングを最小限に抑えることが難しい場合があります。完璧な解決策が得られることはめったにないので、ほとんどの場合をカバーする解決策を特定してください。サポートされている動作が明確に定義されたら、サポートされている動作の範囲内になるように既存のコードを変更します。

于 2013-01-13T22:05:08.067 に答える