21

C ++クラスのオブジェクトが破棄されるときに、C ++クラスによって管理されているBoostスレッドをシャットダウンするための最良の方法は何ですか?構築時にスレッドを作成して開始し、Wake()作業の時間になるとスレッドをウェイクアップするパブリックメソッドを提供するクラスがあります。このWake()メソッドは、BoostミューテックスとBoost条件変数を使用してスレッドに信号を送ります。スレッドプロシージャは条件変数を待機してから作業を行い、待機に戻ります。

現時点では、ブールメンバー変数を「実行中」フラグとして使用して、クラスのデストラクタでこのスレッドをシャットダウンしました。フラグをクリアしてから、条件変数でnotify_one()を呼び出します。次に、スレッドプロシージャが起動し、「実行中」がfalseであることに気づき、戻ります。コードは次のとおりです。

class Worker
{
public:
    Worker();
    ~Worker();
    void Wake();
private:
    Worker(Worker const& rhs);             // prevent copying
    Worker& operator=(Worker const& rhs);  // prevent assignment
    void ThreadProc();
    bool m_Running;
    boost::mutex               m_Mutex;
    boost::condition_variable  m_Condition;
    boost::scoped_ptr<boost::thread> m_pThread;
};

Worker::Worker()
    : m_Running(true)
    , m_Mutex()
    , m_Condition()
    , m_pThread()
{
    m_pThread.reset(new boost::thread(boost::bind(&Worker::ThreadProc, this)));
}

Worker::~Worker()
{
    m_Running = false;
    m_Condition.notify_one();
    m_pThread->join();
}

void Worker::Wake()
{
    boost::lock_guard<boost::mutex> lock(m_Mutex);
    m_Condition.notify_one();
}

void Worker::ThreadProc()
{
    for (;;)
    {
        boost::unique_lock<boost::mutex> lock(m_Mutex);
        m_Condition.wait(lock);
        if (! m_Running) break;
        // do some work here
    }
}

このようにクラスのデストラクタでスレッドをシャットダウンするのは良い考えですか、それともエラー処理やスレッドを強制的に破棄する可能性が高いときに、オブジェクトが破棄される前にユーザーがこれを実行できるようにするパブリックメソッドを提供する必要がありますか?スレッドプロシージャが正常に、または適切なタイミングで戻らない場合はどうなりますか?

デストラクタでオブジェクトの混乱をクリーンアップすることは、ユーザーからの細部への注意が少なくて済むので魅力的ですが(抽象化、ハラー!)、デストラクタですべての責任を負うことが保証できる場合にのみ、デストラクタで何かを行う必要があるようです。物事をうまくそして徹底的にクリーンアップし、クラス外のコードがいつかスレッドがきれいにシャットダウンされたかどうかを知る必要があるかもしれないという小さなチャンスがあります。

また、私が使用しているメカニズム(あるスレッドのスタック上のオブジェクトのメンバー変数に書き込み、別のスレッドでその変数を読み取る)は安全で正常ですか?

4

1 に答える 1

55

リソースの 1 つがスレッドであっても、クラスが破棄されたときにクラスが作成したリソースを解放することをお勧めします。リソースが などのユーザー呼び出しによって明示的に作成された場合は、 などWorker::Start()の明示的な解放方法も必要Worker::Stop()です。また、ユーザーが呼び出さない場合にデストラクタでクリーンアップを実行するか、コンストラクタとデストラクタで呼び出すRAIIWorker::Stop()イディオムを実装するスコープ付きヘルパー クラスをユーザーに提供することもお勧めします。ただし、リソースの割り当てがコンストラクターなどで暗黙的に行われる場合は、リソースの解放も暗黙的に行う必要があり、この責任の最有力候補としてデストラクタを残します。Worker::Start()Worker::Stop()Worker


破壊

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

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

Worker::~Worker()
{
  m_Running = false;
  m_Condition.notify_one();
  try
  {
    m_pThread->join();
  }
  catch ( const boost::thread_interrupted& )
  {
    /* suppressed */ 
  }
}

同時実行

スレッドの管理は難しい場合があります。のような関数の望ましい動作を正確に定義し、Worker::Wake()スレッド化と同期を容易にする型の動作を理解することが重要です。たとえば、 でboost::condition_variable::notify_one()スレッドがブロックされていない場合、 は効果がありませんboost::condition_variable::wait()。の可能な同時パスを調べてみましょうWorker::Wake()

以下は、2 つのシナリオの同時実行性を図示する大雑把な試みです。

  • 操作の順序は上から下に発生します。(つまり、上部の操作は下部の操作の前に発生します。
  • 並行操作は同じ行に書かれています。
  • <>あるスレッドが別のスレッドを起動またはブロック解除しているときに強調表示するために使用されます。たとえば、スレッドがスレッドのブロックを解除A > Bしていることを示します。AB

シナリオ:がブロックされているWorker::Wake()間に呼び出されます。Worker::ThreadProc()m_Condition

その他のスレッド | ワーカー::ThreadProc
-----------------------------------+-------------- ----------------------------
                                   | | ロック ( m_Mutex )
                                   | | `-- m_Mutex.lock()
                                   | | m_Condition::wait( ロック )
                                   | | |-- m_Mutex.unlock()
                                   | | |-- 通知を待つ
ワーカー::ウェイク() | | |
|-- lock( m_Mutex ) | | |
| | `-- m_Mutex.lock() | | |
|-- m_Condition::notify_one() > |-- 通知から復帰
`-- ~lock() | `-- m_Mutex.lock() // ブロック
    `-- m_Mutex.unlock() > `-- // ロックを取得
                                   | | // ここで何らかの作業を行います
                                   | | ~lock() // for ループのスコープの終わり
                                   | | `-- m_Mutex.unlock()

結果:Worker::Wake()かなり迅速に戻り、Worker::ThreadProc実行されます。


シナリオ:がブロックされていないときにWorker::Wake()呼び出されます。Worker::ThreadProc()m_Condition

その他のスレッド | ワーカー::ThreadProc
-----------------------------------+-------------- ----------------------------
                                   | | ロック ( m_Mutex )
                                   | | `-- m_Mutex.lock()
                                   | | m_Condition::wait( ロック )
                                   | | |-- m_Mutex.unlock()
Worker::Wake() > |-- ウェイク アップ
                                   | | `-- m_Mutex.lock()
ワーカー::ウェイク() | // ここで何らかの作業を行います
|-- lock( m_Mutex ) | // まだ作業中...
| | |-- m_Mutex.lock() // ブロック | // システムコールでブロックしないことを願っています
| | | | | | // そしてさらに作業...
| | | | | | ~lock() // for ループのスコープの終わり
| | |-- // まだブロックされている < `-- m_Mutex.unlock()
| | `-- // ロックを取得 | lock( m_Mutex ) // 次の「for」反復。
|-- m_Condition::notify_one() | `-- m_Mutex.lock() // ブロックされる
`-- ~lock() | |-- // まだブロックされている
    `-- m_Mutex.unlock() > `-- // ロックを取得
                                   | | m_Condition::wait( ロック )    
                                   | | |-- m_Mutex.unlock()
                                   | | `-- 通知を待ちます
                                   | | `-- まだ待っている...

結果:機能したようにWorker::Wake()ブロックされましたが、誰も待っていないときにWorker::ThreadProc通知を送信したため、ノーオペレーションでした。m_Condition

これは にとって特に危険ではありませんWorker::Wake()が、 で問題を引き起こす可能性がありますWorker::~Worker()。が作業Worker::~Worker()中に実行されると、スレッドに参加するときに無期限にブロックされる可能性があります。これは、スレッドが通知された時点で待機していない可能性があり、待機が完了した後にのみチェックするためです。Worker::ThreadProcWorker::~Worker()m_ConditionWorker::ThreadProcm_Runningm_Condition


解決に向けて

この例では、次の要件を定義します。

  • Worker::~Worker()std::terminate()呼び出されません。
  • Worker::Wake()仕事をしている間はブロックされませんWorker::ThreadProc
  • が作業を行っていないWorker::Wake()ときに が呼び出されると、作業を行うWorker::ThreadProcように通知Worker::ThreadProcします。
  • が作業Worker::Wake()中に が呼び出された場合、作業の別の反復を実行するように通知します。Worker::ThreadProcWorker::ThreadProc
  • Worker::Wake()while Worker::ThreadProcis doing work を複数回呼び出すとWorker::ThreadProc、追加の作業の繰り返しが 1 回実行されます。

コード:

#include <boost/thread.hpp>
 
class Worker
{
public:
  Worker();
  ~Worker();
  void Wake();
private:
  Worker(Worker const& rhs);             // prevent copying
  Worker& operator=(Worker const& rhs);  // prevent assignment
  void ThreadProc();
 
  enum state { HAS_WORK, NO_WORK, SHUTDOWN };
  
  state                            m_State;
  boost::mutex                     m_Mutex;
  boost::condition_variable        m_Condition;
  boost::thread                    m_Thread;
};
 
Worker::Worker()
  : m_State(NO_WORK)
  , m_Mutex()
  , m_Condition()
  , m_Thread()
{
  m_Thread = boost::thread(&Worker::ThreadProc, this);
}
 
Worker::~Worker()
{
  // Create scope so that the mutex is only locked when changing state and
  // notifying the condition.  It would result in a deadlock if the lock was
  // still held by this function when trying to join the thread.
  {
    boost::lock_guard<boost::mutex> lock(m_Mutex);
    m_State = SHUTDOWN;
    m_Condition.notify_one();
  }
  try { m_Thread.join(); }
  catch ( const boost::thread_interrupted& ) { /* suppress */ };
}
 
void Worker::Wake()
{
  boost::lock_guard<boost::mutex> lock(m_Mutex);
  m_State = HAS_WORK;
  m_Condition.notify_one();
}
 
void Worker::ThreadProc()
{
  for (;;)
  {
    // Create scope to only lock the mutex when checking for the state.  Do
    // not continue to hold the mutex wile doing busy work.
    {
      boost::unique_lock<boost::mutex> lock(m_Mutex);
      // While there is no work (implies not shutting down), then wait on
      // the condition.
      while (NO_WORK == m_State)
      {
        m_Condition.wait(lock);
        // Will wake up from either Wake() or ~Worker() signaling the condition
        // variable.  At that point, m_State will either be HAS_WORK or
        // SHUTDOWN.
      }
      // On shutdown, break out of the for loop.
      if (SHUTDOWN == m_State) break;
      // Set state to indicate no work is queued.
      m_State = NO_WORK;
    }
 
    // do some work here
  }
}

注: 個人的な好みとしてboost::thread、ヒープに割り当てないことを選択したため、boost::scoped_ptr. Not-a-Threadを参照するboost::threadデフォルトのコンストラクターがあり、move-assignableです。

于 2012-06-29T22:53:28.477 に答える