リソースの 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
していることを示します。A
B
シナリオ:がブロックされている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::ThreadProc
Worker::~Worker()
m_Condition
Worker::ThreadProc
m_Running
m_Condition
解決に向けて
この例では、次の要件を定義します。
Worker::~Worker()
std::terminate()
呼び出されません。
Worker::Wake()
仕事をしている間はブロックされませんWorker::ThreadProc
。
- が作業を行っていない
Worker::Wake()
ときに が呼び出されると、作業を行うWorker::ThreadProc
ように通知Worker::ThreadProc
します。
- が作業
Worker::Wake()
中に が呼び出された場合、作業の別の反復を実行するように通知します。Worker::ThreadProc
Worker::ThreadProc
Worker::Wake()
while Worker::ThreadProc
is 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です。