初めに†
- C++11 では thread がサポートされている。→ std::thread class を使う
- gcc では build 時に -pthread オプションを要求される場合がある(ビルドが通っても実行できなかった場合には、これを忘れていないか、まず疑ってみる)
std::thread†
- C++11 では、 std::thread class を用いてスレッドの生成と管理を行う。
- std::thread は、 copy 不可・move 不可 となっている。
thread の作成†
- 関数や関数オブジェクトの呼び出し演算子が仮引数を取る場合、 std::thread のコンストラクタの第2引数以降に関数や関数オブジェクトの関数呼び出し演算子へ与える実引数を渡す。
- std::thread のコンストラクタに渡された実引数は、受け取る関数が参照渡しとして宣言されていたとしても、コピーされて std::thread クラスのオブジェクト内に保存される。
- 上記コピーを避けたい場合は、実引数をムーブするか、 std::ref() 関数でくるんで渡す。
- std::ref() を使用した場合、作成した thread に参照が渡されるので、元のオブジェクトの寿命に注意すること。
- thread 内で例外が発生し、 catch されないまま thread 外に送出されると、std::terminate() が呼び出され、プログラムが強制終了する。
クラスメンバ関数をスレッド関数にする場合†
- class member な関数は、そのままではスレッド関数に指定出来ない。スレッド生成時に this を渡してやることで生成可能になる。
class ClsA {
public:
ClsA() {}
virtual ~ClsA() {}
void Init();
...
void ThreadMain();
private:
std::thread::id m_thread_id_main;
...
};
// スレッドの開始
void ClsA::Init()
{
// スレッド生成時に this を渡してやる
std::thread main_thread( ThreadMain(), this );
m_thread_id_main = main_thread.get_id();
// detach する。
main_thread.detach();
....
}
thread 終了を待機する†
thread を手放す†
thread の識別†
- std::thread::id std::thread::get_id() を呼ぶ
void foo( ostringstream &os ) {
os << "foo(): " << this_thread::get_id() << endl;
}
void bar( ostringstream &os ) {
os << "bar(): " << this_thread::get_id() << endl;
}
ostringstream os_foo;
ostringstream os_bar;
thread th1( foo(), ref( os_foo ) );
thread th2( bar(), ref( os_bar ) );
// それぞれの thread ID を得る
thread::id id1 = th1.get_id();
thread::id id2 = th2.get_id();
thread::id id3 = this_thread::get_id();
assert( id1 != is2 );
assert( id1 != is3 );
assert( id2 != is3 );
th1.join();
th2.join();
cout << os_foo.str();
cout << os_bar.str();
cout << "main thread : " << this_thread::get_id() << endl;
現在の thread の処理を明け渡す†
void std::yield() noexcept;
現在の thread をスリープする†
並行実行できる thread の数を取得する†
thread を排他制御する†
mutex†
std::mutex†
- 再帰的にロックは掛けられない →1スレッド内で同じオブジェクトに2回ロックを掛けるとデッドロックが発生する。
- デッドロックにはまった場合、実装系がデッドロックを発見出来る場合には、 std::errc::resource_deadlock_would_occur を設定した std::system_error 例外が送出される可能性がある。
I/F†
- lock()
- オブジェクトに対しロックを掛ける
- 呼び出した時点で、同じオブジェクトに対し既にロックがかけられている場合、そのロックが解除されるまでブロックされる
- try_lock():
- ロックを試行する
状態 | try_loclk() 呼び出し結果 | 備考 |
どのスレッドもそのオブジェクトにロックをかけていない時 | ロックをかけ、true を返す | |
他のスレッドから既にロックがかけられている場合 | ロックに失敗し、false が返る | |
std::recursive_mutex†
- 再帰的にロック出来る mutex
- スレッド間で排他的だが、同一スレッド内からは同一オブジェクトに対し複数回のロックを掛けることが出来る。
- 何回までロックを掛けられるかは規定がない。上限を超えた場合の挙動は
- try_lock(): false が返る
- lock() : std::system_error 例外を送出
std::timed_mutex†
- タイムアウト付き mutex
- lock() / try_lock() も使用出来る
- タイムアウト指定出来るロック関数が用意されている
- try_lock_for() : 指定時間だけロックを試行する
- try_lock_until() : 指定時刻までロックを試行する
- タイムアウト付きロック関数は、タイムアウトまでにロック出来た場合には true, 出来なかった場合は false を返す。
std::timed_recursive_mutex†
- 再帰的にロックを掛けられる、タイムアウト付き mutex
- std::recursivemutex, std::timed_mutex の両方の機能を備えている。
リソースのロックを管理する†
std::lock_guard†
- ロックのインスタンスを生成し、それが有効な間をロックの対象とする
- よって、明示的なロック解除の I/F は使わない
#include <mutex>
{
std::mutex mtx;
{
std::lock_guard<mutex> lk( mtx ); // ここからロックが掛かる
// 処理...
} // lk の有効なスコープを抜け、lk のインスタンスが消滅すると同時にロック解除となる
}
std::unique_lock†
- コンストラクタ第2引数に std::defer_lock 変数を渡すと、ロックのタイミングを遅延する
- 後からロックを取得するには、 lock() / try_lock() を呼び出す
- 使用する mutex が try_lock_for(), try_lock_until() メンバ関数を持っている場合、コンストラクタ第2仮引数に std::chrono::duration / std::chrono::time_point クラスのオブジェクトを渡し、タイムアウト付きでロック取得を試行出来る。
- コンストラクタに std::defer_lock が渡され、ロックを取得するタイミングが遅延されている場合、 try_loc_for() / try_lock_until() メンバを呼び出し、タイムアウト付きでロック取得を試行出来る。
- release() : 管理しているオブジェクトを手放す
- unlock() : 任意のタイミングでロックを解除する
- owns_lock() : 現在ロックを取得しているかどうかを確認出来る
複数のリソースをロックする†
ロックせずに吐いたアクセスする†
スレッドセーフに1度だけ関数を呼び出す†
条件変数を使用する†
thread をまたいで値や例外を受け渡す†
- スレッド間で非同期に値や例外を受け渡す方法として、 std::promise / std::future が提供されている
- 以下は、 std::string をやり取りする例:
#include <iostream>
#include <thread>
#include<future>
static std::mutex g_print_mutex;
void LockPrintf( const std::string & str )
{
std::lock_guard<std::mutex> lk( g_print_mutex );
std::cout << str << std::endl;
}
void ThreadFuncA( std::promise<std::string> pr )
{
std::string sendstr ="from ThreadFuncA";
pr.set_value( sendstr );
LockPrintf( "ThreadFuncA" );
}
int main( int argc, char * argv[] )
{
std::promise<std::string> prom;
std::future<std::string> futu = prom.get_future();
std::thread th_a( ThreadFuncA, move( prom ) );
std::string future_string = "Rcv from A: " + futu.get();
LockPrintf( future_string );
th_a.join();
return( 0 );
}
std::promise --- 他のスレッドに渡す値や例外を設定する†
std::future --- 他のスレッドからセットされた値や例外を取り出す†
非同期処理をする†
スレッドローカル変数を使用する†