#author("2018-03-04T01:14:31+00:00","","") #topicpath ////////////////////////////////////////////////////////////////////////////// * 目次 [#z44d9e2e] #contents(); ////////////////////////////////////////////////////////////////////////////// * 初めに [#s8cb5470] - C++11 では thread がサポートされている。→ std::thread class を使う - gcc では build 時に -pthread オプションを要求される場合がある(ビルドが通っても実行できなかった場合には、これを忘れていないか、まず疑ってみる) ////////////////////////////////////////////////////////////////////////////// * std::thread [#std-thread] - C++11 では、 std::thread class を用いてスレッドの生成と管理を行う。 - std::thread は、 ''copy 不可・move 不可'' となっている。 ////////////////////////////////////////////////////////////////////////////// * thread の作成 [#create-thread] - 関数 foo(), bar() をそれぞれ別の thread で実行する // 排他出力処理 mutex print_mutex; void print( const string &s ) { lock_guard<mutex> lk(print_mutex); cout << s << endl; } void foo() { print( "foo" ); } void bar() { print( "bar" ); } // foo(), bar() をそれぞれ別の thread で実行 thread th1( foo ); thread th2( bar ); th1.join(); th2.join(); - 関数や関数オブジェクトの呼び出し演算子が仮引数を取る場合、 std::thread のコンストラクタの第2引数以降に関数や関数オブジェクトの関数呼び出し演算子へ与える実引数を渡す。 - std::thread のコンストラクタに渡された実引数は、受け取る関数が参照渡しとして宣言されていたとしても、コピーされて std::thread クラスのオブジェクト内に保存される。 -- 上記コピーを避けたい場合は、実引数をムーブするか、 std::ref() 関数でくるんで渡す。 -- std::ref() を使用した場合、作成した thread に参照が渡されるので、元のオブジェクトの寿命に注意すること。 - thread 内で例外が発生し、 catch されないまま thread 外に送出されると、std::terminate() が呼び出され、プログラムが強制終了する。 //============================================================================ ** クラスメンバ関数をスレッド関数にする場合 [#vf4bce69] - class member な関数は、そのままではスレッド関数に指定出来ない。スレッド生成時に &color(purple){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 終了を待機する [#join] - thread の終了を待機するところで std::thread::join() を呼ぶ。 std::thread th1( foo() ); th1.join(); cout << "th1 is joined" << endl; - join() 呼び出しが完了するまで、 join() の実行元スレッドはブロックされる。 - join() の呼び出しが完了すると、そのオブジェクト(上記例では th1)はどのスレッドも管理していない状態になる。 - 既に join() の呼び出しが完了しているオブジェクトに対して join() を呼び出すと、 std::errc::invalid_argument をエラーコードに設定した std::system_error 例外が送出される。 - std::thread::joinable() の返り値を見ることによって、そのオブジェクトの join() を問題なく呼び出せるかどうかを確認することが出来る。 - join() 呼び出し可能なオブジェクトが破棄される場合、 std::thread::detach() や std::thread::join() が自動的に呼び出されず、 std::terminate() が呼び出されてプログラムが即時に終了することになっている。 - join() 可能なオブジェクトは、破棄される前に必ず join() するか detach() を呼び出すようにしなければならない。 ////////////////////////////////////////////////////////////////////////////// * thread を手放す [#t3ea7895] - void srd::thread::detach() を実行する using namespace std; void foo() { // 時間の掛かる処理 ... } void bar() { thread th1( foo() ); th1.detach(); // スレッドを手放す } // 関数 bar() が終了しても、スレッド foo() は別スレッドにて実行中 - std::thread::detach() が呼び出されると、そのオブジェクト(上記例の th1)はどのスレッドも管理していない状態になる。 - 既に detach() が呼び出されたオブジェクトに対して detach() を呼び出すと、 std::errc::invalid_argument をエラーコードに設定された std::system_error 例外が送出される。 - std::exit() が呼ばれると、実行中のスレッドがあってもプログラムは終了する。この時、実行中のスレッドから、プログラム終了に伴い破棄されたオブジェクトにアクセスすると、未定義の動作を引き起こす。 ////////////////////////////////////////////////////////////////////////////// * thread の識別 [#x6a39824] - 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; - std::thread::id は文字列型なので、無効な値(実行中でないスレッドオブジェクトの get_id() の返り値)をプリントすると、下記のような文字列が出力される: thread::id of a non-executing thread - std::thread::id は、std::thread が管理する一意な ID - 実行が終了したスレッドの std::thread::id は、再利用される可能性がある。そのため、スレッドの寿命を越えて std::thread::id を使用してはならない。 ////////////////////////////////////////////////////////////////////////////// * 現在の thread の処理を明け渡す [#s20f6dff] void std::yield() noexcept; ////////////////////////////////////////////////////////////////////////////// * 現在の thread をスリープする [#q1284bbf] ////////////////////////////////////////////////////////////////////////////// * 並行実行できる thread の数を取得する [#te2d6f95] ////////////////////////////////////////////////////////////////////////////// * thread を排他制御する [#d81456e1] ////////////////////////////////////////////////////////////////////////////// * mutex [#mutex] //============================================================================ ** std::mutex [#std-mutex] - 再帰的にロックは掛けられない →1スレッド内で同じオブジェクトに2回ロックを掛けるとデッドロックが発生する。 - デッドロックにはまった場合、実装系がデッドロックを発見出来る場合には、 std::errc::resource_deadlock_would_occur を設定した std::system_error 例外が送出される可能性がある。 //--------------------------------------------------------------------------- *** I/F [#c7c754d8] - lock() -- オブジェクトに対しロックを掛ける -- 呼び出した時点で、同じオブジェクトに対し既にロックがかけられている場合、そのロックが解除されるまでブロックされる - try_lock(): -- ロックを試行する |~状態 |~try_loclk() 呼び出し結果 |~備考 | |どのスレッドもそのオブジェクトにロックをかけていない時 |ロックをかけ、true を返す | | |他のスレッドから既にロックがかけられている場合 |ロックに失敗し、false が返る | | - unlock() -- オブジェクトに掛けたロックを解除する //============================================================================ ** std::recursive_mutex [#o3baca9e] - 再帰的にロック出来る mutex - スレッド間で排他的だが、同一スレッド内からは同一オブジェクトに対し複数回のロックを掛けることが出来る。 - 何回までロックを掛けられるかは規定がない。上限を超えた場合の挙動は -- try_lock(): false が返る -- lock() : std::system_error 例外を送出 //============================================================================ ** std::timed_mutex [#md13522b] - タイムアウト付き mutex - lock() / try_lock() も使用出来る - タイムアウト指定出来るロック関数が用意されている -- try_lock_for() : 指定時間だけロックを試行する -- try_lock_until() : 指定時刻までロックを試行する -- タイムアウト付きロック関数は、タイムアウトまでにロック出来た場合には true, 出来なかった場合は false を返す。 //============================================================================ ** std::timed_recursive_mutex [#yf9f741a] - 再帰的にロックを掛けられる、タイムアウト付き mutex - std::recursivemutex, std::timed_mutex の両方の機能を備えている。 ////////////////////////////////////////////////////////////////////////////// * リソースのロックを管理する [#o619d861] //============================================================================ ** std::lock_guard [#t771f241] - ロックのインスタンスを生成し、それが有効な間をロックの対象とする - よって、明示的なロック解除の I/F は使わない #include <mutex> { std::mutex mtx; { std::lock_guard<mutex> lk( mtx ); // ここからロックが掛かる // 処理... } // lk の有効なスコープを抜け、lk のインスタンスが消滅すると同時にロック解除となる } //============================================================================ ** std::unique_lock [#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() : 現在ロックを取得しているかどうかを確認出来る ////////////////////////////////////////////////////////////////////////////// * 複数のリソースをロックする [#w31f69a0] ////////////////////////////////////////////////////////////////////////////// * ロックせずに吐いたアクセスする [#p87c1c1a] ////////////////////////////////////////////////////////////////////////////// * スレッドセーフに1度だけ関数を呼び出す [#g8f42eac] ////////////////////////////////////////////////////////////////////////////// * 条件変数を使用する [#kb3e1964] ////////////////////////////////////////////////////////////////////////////// * thread をまたいで値や例外を受け渡す [#l67d5b92] - スレッド間で非同期に値や例外を受け渡す方法として、 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 --- 他のスレッドに渡す値や例外を設定する [#o1b17e29] //============================================================================ ** std::future --- 他のスレッドからセットされた値や例外を取り出す [#x76936e3] ////////////////////////////////////////////////////////////////////////////// * 非同期処理をする [#kd28f4df] ////////////////////////////////////////////////////////////////////////////// * スレッドローカル変数を使用する [#k24f952f]