Your SlideShare is downloading. ×
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
規格書で読むC++11のスレッド
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

規格書で読むC++11のスレッド

6,702

Published on

2013/07/13に、Sapporo.cppとCLR/Hで合同で開催した勉強会で発表した資料。

2013/07/13に、Sapporo.cppとCLR/Hで合同で開催した勉強会で発表した資料。

Published in: Technology
0 Comments
12 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
6,702
On Slideshare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
62
Comments
0
Likes
12
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. 2013/07/13 規格書で読むC++11のスレッド @hotwatermorning 1
  • 2. 発表者自己紹介 ✤ @hotwatermorning ✤ DTMer ✤ C++ポケットリファレンス書きました!(共著) ✤ Amazonのなか見検索に対応! 2 http://www.amazon.co.jp/C-%E3%83%9D %E3%82%B1%E3%83%83%E3%83%88%E3%83%AA %E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9- %E9%AB%98%E6%A9%8B-%E6%99%B6/dp/4774157155
  • 3. 本日のセッションを始める前に ✤ 今日のセッションはC++の規格と絡むので、手元 に規格書があると便利です。 ✤ 本日は規格書の代わりに、N3337を使用しま す。 ✤ ※PDF,割とサイズが大きいので注意 3
  • 4. C++のスレッド 4
  • 5. Free Lunch Is Over 5 ✤ タダ飯の時代は終わった ✤ 2005年 マイクロソフトの Software Architect である Herb Sutterの言葉 ✤ CPUコアのクロック数向上が頭打ちとなり、時 を待てば自ずとソフトウェアの性能が上がるとい う時代の終わり ✤ →並行プログラミングが重要に
  • 6. C++のスレッド ✤ 2011年に策定された新規格(通称:C++11) から、C++にマルチスレッドサポートが 導入された。 ✤ これによって、ポータブルなコードで マルチスレッドアプリケーションをかけるように なった。 6
  • 7. #include <iostream> #include <thread> #include <vector> #include <functional> int main() { std::vector<int> data = GetSomeData(); int sum; std::thread th( [](std::vector<int> const &data_, int &sum_) { sum_ = 0; for(auto n : data_) { sum_ += n; } }, std::cref(data), std::ref(sum) ); th.join(); std::cout << "sum of data is : " << sum << std::endl; } C++のスレッド 7
  • 8. C++03との違い 8
  • 9. C++03までのスレッド 9 ✤ 規格で、マルチスレッドをサポートしたメモリモ デルや実行スレッドが定義されていなかった。 ✤ そのため、これまでC++でマルチスレッド処理を するためには、各プロセッサーのメモリモデルや 各処理系のスレッドの定義に依存したコードを書 く必要があった。
  • 10. C++11からのスレッド 10 ✤ 規格で、マルチスレッドをサポートしたメモリモ デルや実行スレッドが定義された。 ✤ マルチスレッドための言語機能やライブラリも導 入された。 ✤ 標準機能のポータブルなコードでマルチスレッド が実現できるようになった。 ✤ スレッドライブラリも、最新のC++の知見をふん だんに使用しているので、洗練されている。
  • 11. ライブラリ 11 ✤ thread ✤ mutex/lock ✤ future/promise ✤ async/packaged_task ✤ condition_variable ✤ call_once ✤ atomic
  • 12. 言語機能 12 ✤ static変数の初期化 ✤ thread_local変数のサポート ✤ スレッドを超えた例外の伝播(std::exception_ptr) ✤ などなど
  • 13. 本日やる内容 13
  • 14. 本日やる内容 14 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  • 15. 本日やる内容 15 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  • 16. thread ✤ スレッドを扱うクラス ( 30.3.1) ✤ threadクラスは新たな実行スレッドを作成したり、その スレッドの終了を待機したり、その他スレッドの状態を問 い合せる操作のメカニズムを提供します。 ✤ スレッドの作成やスレッドハンドルの管理を行う ✤ そのためプログラマが常に明示的にスレッドの作成や ハンドルの管理を意識する必要がない ✤ Not Copyable / Movable 16
  • 17. スレッドの作成 ✤ threadクラスのコンストラクタで、第一引数に関 数や関数オブジェクトを渡し、第二引数以降にそ の関数に適用させたい引数を渡す( 30.3.1.2/3) ( 20.8.2/1) 17
  • 18. #include <iostream> #include <thread> void foo(){ std::cout << "Hello" << std::endl; } int main() { // 別スレッドでfoo関数を起動 std::thread th(foo); // スレッドの終了を待機 th.join(); } スレッドの作成 18
  • 19. template <class F, class ...Args> explicit thread(F&& f, Args&&... args); // というコンストラクタについて、 INVOKE ( DECAY_COPY( std::forward<F>(f) ), DECAY_COPY( std::forward<Args>(args) )... ) // が有効であるような関数や引数を渡せる コンストラクタの定義 19
  • 20. DECAY_COPY() ✤ 引数に渡されたオブジェクトをコピーして受け渡 す操作を表す。( 30.2.6) ✤ ただし、rvalueなオブジェクトはそのままムーブ されたり、配列は先頭要素へのポインタにするな どの変換が行われる。 ✤ 詳しくは http://d.hatena.ne.jp/yohhoy/20120306/p1 20
  • 21. INVOKE() ✤ 関数やメンバ関数へのポインタや関数オブジェク ト(Functor)やラムダ式など、なんらか呼び出し 可能なものを第1引数に取り、続く引数を適用し て呼び出す操作を表す。( 20.8.2) ✤ https://sites.google.com/site/cpprefjp/reference/functional/invoke ✤ http://twitter.com/Cryolite/status/216814363221303296 21
  • 22. // INVOKEが表す仮想的な呼び出し操作は INVOKE(f, t1, t2, ..., tN); // 実際には以下から適切なものが呼び出される (t1.*f)(t2, ..., tN); ((*t1).*f)(t2, ..., tN); f(t1, t2, ..., tN); INVOKE() 22
  • 23. struct Foo { // operator()をもつクラス // → 関数オブジェクト(別名:ファンクタ) void operator() (std::string msg) const { std::cout << "Hello " << msg << std::endl; } }; Foo foo; std::string msg = "World!"; foo("World"); // Hello World! //関数のように呼び出せる INVOKE() 23
  • 24. int main() { std::string msg = "World!"; Foo foo; // 関数のように呼び出せるものは // スレッドに渡せる std::thread th(foo, msg); // Hello World! //スレッドの終了を待機 th.join(); } INVOKE() 24
  • 25. スレッドに参照を渡す ✤ スレッドの引数にオブジェクトの参照を渡そうと すると、DECAY_COPY()によって、コンパイル エラーとなる。 ✤ 参照型はコピーもムーブもできないため ✤ そのため、DECAY_COPY()に渡す前に参照を std::ref()やstd::cref()で包んで渡すようにする。 ✤ http://d.hatena.ne.jp/yohhoy/20120306/p1 25
  • 26. #include <thread> #include <iostream> #include <functional> void add(int a, int b, int &c) { c = a + b; } int main() { int result; std::thread th1(add, 1, 2, std::ref(result)); th1.join(); std::cout << result << std::endl; } スレッドに参照を渡す 26
  • 27. thread ✤ スレッドを扱うクラス ( 30.3.1) ✤ threadクラスは新たな実行スレッドを作成したり、その スレッドの終了を待機したり、その他スレッドの状態を問 い合せる操作のメカニズムを提供します。 ✤ スレッドの作成やスレッドハンドルの管理を行う ✤ そのためプログラマが常に明示的にスレッドの作成や ハンドルの管理を意識する必要がない ✤ Not Copyable / Movable 27
  • 28. Copyable Type ✤ CopyConstructible要件 と CopyAssignable要件 を満たす型 ( 17.6.3.1) ✤ データをコピーして、オブジェクト間で同じ状態 を実現できる ✤ (Copyable自体はC++規格に定義された用語で はないが、上の2つの性質をまとめてCopyable という) 28
  • 29. // CopyConstructible T u = v; T(v); // CopyAssignable u = v; Copyable Type 29
  • 30. Movable Type ✤ MoveConstructible要件 と MoveAssignable要件 を満たす型 ( 17.6.3.1) ✤ データ(の所有権)を移動して、オブジェクト間 でデータを受け渡せる ✤ (Movable自体はC++規格に定義された用語で はないが、上の2つの性質をまとめてMovableと いう) 30
  • 31. // MoveConstructible T u = SomeFunctionReturnsT(); T(SomeFunctionReturnsT()); // MoveAssignable u = SomeFunctionReturnsT(); Movable Type 31
  • 32. // MoveConstructible T u = std::move(t1); T(std::move(t2)); // MoveAssignable u = std::move(t3); Movable Type 32
  • 33. #include <cassert> #include <thread> #include <iostream> void foo(int a, int b) { std::cout << a << ", " << b << std::endl; } int main() { std::thread th1(foo, 1, 2); // コンパイルエラー std::thread th2 = th1; // ムーブなら問題ない std::thread th3 = std::move(th1); // ムーブ元のth1はスレッドを手放している。 assert(th1.get_id() == std::thread().get_id()); th3.join(); } スレッドの受け渡し 33
  • 34. thread ✤ threadクラスのデストラクタと、スレッド管理 の問題 34
  • 35. デストラクタとjoin/detach 35 ✤ thread::join()はスレッドの終了を待機し、 threadオブジェクトを初期化する関数 ✤ thread::detach()はスレッドの管理を手放し、 threadオブジェクトを初期化する関数
  • 36. デストラクタとjoin/detach 36 ✤ なんらかのスレッドを管理しているthreadオブ ジェクトが、join()もdetach()も呼び出さずにデ ストラクトされると、std::terminate()を呼び出 して、プログラムが強制終了する。 ✤ デストラクタで自動的にjoin()やdetach()を呼び出すこと にすると、プログラマの意図しないjoin()やdetach()の 呼び出しが発生し、予期しないバグを引き起こす可能性が あるため。 ✤ http://d.hatena.ne.jp/yohhoy/20120209/p1 ✤ http://d.hatena.ne.jp/yohhoy/20120211/p1 ✤ http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2802.html
  • 37. void worker(SomeParam p) { doSomething(); } void foo(SomeParam p) { std::thread th(worker, p); // このまま関数を抜ける // →std::terminate()で強制終了 } デストラクタとjoin/detach 37
  • 38. デストラクタとjoin/detach 38 ✤ このままでは、あまりに活用しにくいように思わ れるが、そもそもthreadクラスはC++で スレッドを扱うための最もプリミティブな機能な ので、このようになっている。
  • 39. デストラクタとjoin/detach 39 ✤ マルチスレッドや非同期処理のより高級な仕組み としてstd::asyncやstd::future/std::promiseな どが用意されている。 ✤ ただしマルチスレッドプログラミングに便利な機 能が充分に っているとは言えない。 ✤ なのでそういうのが必要な場合はTBBや Microsoft PPLなどを使ったほうがいいかも? ✤ http://corensic.wordpress.com/2011/10/10/async-tasks-in-c11-not-quite- there-yet/ ✤ http://d.hatena.ne.jp/yohhoy/20120417/p1
  • 40. デストラクタとjoin/detach 40 ✤ 明示的にjoin()/detach()しなければプログラムが 強制終了する挙動は、そのままでは例外機構との 相性が悪い。 ✤ RAIIイディオムを用いて、自動でjoinを行う方法 などが以下に紹介されている。 ✤ http://akrzemi1.wordpress.com/2012/11/14/not-using-stdthread/ ✤ http://www.boost.org/doc/html/thread/ScopedThreads.html
  • 41. 本日やる内容 41 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  • 42. Mutex/Lock 42 ✤ 排他処理を実現する仕組み
  • 43. Mutexクラス 43 ✤ スレッド間で排他的に所有されるリソースを表す クラス ( 30.4) ✤ ミューテックスオブジェクトは、データ競合に対する 保護を容易にし、execution agents間での安全なデー タ同期を可能にします。
  • 44. Mutexクラス 44 ✤ 標準で定義されているMutexクラスは以下の4つ ✤ std::mutex ✤ std::recursive_mutex ✤ std::timed_mutex ✤ std::recursive_timed_mutex
  • 45. std::mutex 45 ✤ 再帰的ロック不可なミューテックス ( 30.4.1.2.1) ✤ Lockable
  • 46. std::recursive_mutex 46 ✤ 再帰的ロック可能なミューテックス ( 30.4.1.2.2) ✤ Lockable ✤ Window APIのMutexやCritical Sectionの挙動 と同じ
  • 47. Lockable Type 47 ✤ 排他処理のために、標準規格のスレッドライブラ リから使用されるオブジェクトに必要とされる要 件を満たす型 ( 30.2.5.3) ✤ 以下の3つのメンバ関数を持つなど ✤ m.lock() ✤ m.try_lock() ✤ m.unlock()
  • 48. SomeLockableType m; // 所有権を取得 m.lock(); // 所有権の取得を試行 m.try_lock(); // 所有権を手放す m.unlock(); Lockable Type 48
  • 49. std::timed_mutex 49 ✤ 再帰的ロック不可な 時間制限機能付きミューテックス ✤ ロック処理を制限時間まで試行する ✤ TimedLockable
  • 50. std::recursive_timed_mutex 50 ✤ 再帰的ロック可能な 時間制限機能付きミューテックス ✤ ロック処理を制限時間まで試行する ✤ TimedLockable
  • 51. TimedLockable Type 51 ✤ 排他処理のために、標準規格のスレッドライブラ リから使用されるオブジェクトに必要とされる要 件を満たす型 ( 30.2.5.3) ✤ LockableTypeの性質に加え、以下のメンバ関数 を持つなど ✤ m.try_lock_for() ✤ m.try_lock_until()
  • 52. #include <chrono> SomeTimedLockableType m; // 指定時間だけ所有権の取得を試行 m.try_lock_for( std::chrono::seconds(3) ); // 指定時刻まで所有権の取得を試行 m.try_lock_until( std::chrono::steady_clock()::now() + std::chrono::seconds(3) ); Lockable Type 52
  • 53. Lockクラス 53 ✤ Lockableなオブジェクトを管理するRAIIクラス ( 30.4.2) ✤ lock とはLockableなオブジェクトの参照を保持し、ス コープを抜けるなどのデストラクト時には、その Lockableなオブジェクトをunlockするようなオブジェク トです。 execution agentはlockableオブジェクト(のロック)の 所有権を、例外安全のマナーに則って管理するための助け として、この"lock"を使用できます。
  • 54. Lockクラス 54 ✤ それ自身は、排他処理のための直接的な機能は持 たずに、参照として保持するMutexクラスを管理 するだけ。
  • 55. Lockクラス 55 ✤ 標準規格で定義されているLockクラス ✤ std::lock_guard<Mutex> ✤ std::unique_lock<Mutex>
  • 56. std::lock_guard<Mutex> 56 ✤ ミューテックスのシンプルな管理機構を実現する クラステンプレート ✤ テンプレート引数Mutexは、BasicLockableでな ければならない
  • 57. BasicLockable Type 57 ✤ 排他処理のために、標準規格のスレッドライブラ リから使用されるオブジェクトに必要とされる要 件を満たす型 ( 30.2.5.3) ✤ 以下の2つのメンバ関数を持つなど ✤ m.lock() ✤ m.unlock() ✤ 前述のLockable Typeは、BasicLockable Type の性質を含んでいる
  • 58. SomeBasicLockableType m; // 所有権を取得 m.lock(); // 所有権を手放す m.unlock(); BasicLockable Type 58
  • 59. struct Worker { //... void process() { for(int i = 0; i < 100; ++i) { //このスコープ内だけ排他処理する std::lock_guard<std::mutex> lock(mutex_); data_ = doSomething(data_); } } private: std::mutex mutex_; int data_; // ... }; std::lock_guard<Mutex> 59
  • 60. Lockクラス 60 ✤ 標準規格で定義されているLockクラス ✤ std::lock_guard<Mutex> ✤ std::unique_lock<Mutex>
  • 61. std::unique_lock<Mutex> 61 ✤ lock_guardよりも高級な処理ができるクラス ✤ Mutexオブジェクトの再割当て、ムーブ、try_lock_*によ るロックの試行
  • 62. Lockクラス 62 ✤ Lockクラスによって、プログラマが明示的に lock()/unlock()を対応付けて管理する必要がなく なる。 ✤ RAIIというイディオムによって、例外安全性も高 まる。 ✤ http://d.hatena.ne.jp/heisseswasser/20130508/1367988794
  • 63. 本日やる内容 63 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  • 64. future/promise 64 ✤ 並行プログラミングのPromise/Futureパターン を実現する。 ✤ あるスレッドから、同じあるいは異なるスレッド で走る関数の結果を受け取るためのコンポーネン ト。マルチスレッドプログラミングのためだけで はなく、シングルスレッドプログラムでも、非同 期処理に役立つ。( 30.6.1)
  • 65. future/promise 65 ✤ std::promiseクラスとstd::futureクラスの対応す るオブジェクト同士は、一つのshared stateを 共有する。( 30.6.4) ✤ データを作る側はpromiseに値を設定し、データ を受け取る側ではfutureから値を取得する
  • 66. void sum_async( std::vector<int> const &data, std::promise<int> promise) { int sum = 0; for(auto n : data) { sum += n; } promise.set_value(sum); // promiseにデータをセットし } int main() { std::vector<int> data = { 1, 2, 3 }; std::promise<int> p; std::future<int> f = p.get_future(); std::thread th( sum_async, std::cref(data), std::move(p)); th.detach(); std::cout << f.get() << std::endl; // futureで受け取る } future/promise 66
  • 67. std::promise<R> 67 ✤ set_value(R const &) ✤ 非同期処理の結果として値を設定 ✤ この形式の他に、Rの型によって異なるシグネチャのものが存在する。 ✤ set_exception(std::exception_ptr) ✤ 非同期処理の結果として例外を設定
  • 68. std::future<R> 68 ✤ get() ✤ 非同期処理の結果を取得する ✤ この形式の他に、Rの型によって戻り値やシグネチャが異なるものが存在する。 ✤ promiseで例外が設定された場合は、get()の呼び出し で、もとの例外が再送出される ✤ wait() ✤ 非同期処理の結果がpromiseに設定されるまで処理をブ ロックする(値/例外はまだ取得しない)
  • 69. void sum_async( std::vector<int> const &data, std::promise<int> promise) { if(data.empty()) { std::exception_ptr pe = std::make_exception_ptr( UnexpectedDataLengthError() );     // エラーを表すときは、promiseに例外をセットする promise.set_exception(pe); } else { int sum = 0; for(auto n : data) { sum += n; } promise.set_value(sum); } } 例外の受け渡し(セット側) 69
  • 70. int main() { std::vector<int> data = {}; std::promise<int> p; std::future<int> f = p.get_future(); std::thread th( sum_async, std::cref(data), std::move(p)); th.detach(); try { std::cout << f.get() << std::endl; // futureのget()で例外が再送出される } catch(UnexpectedDataLengthError &e) { std::cout << e.what() << std::endl; } } 例外の受け渡し(取得側) 70
  • 71. 本日やる内容 71 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  • 72. condition_variable 72 ✤ 条件変数という、スレッド間で同期をとる仕組み ( 30.5/1) ✤ 条件変数は、ある条件が満たされた事によって他のス レッドから通知を受けたり、システム時間が設定時刻に到 達するまでスレッドをブロックするのに使用される同期プ リミティブを提供します。
  • 73. 条件変数の動作原理 73 ✤ あるスレッドでミューテックスにロックをかけ、 条件変数にミューテックスを渡す。 ✤ 条件変数はミューテックスのロックを解放し、処 理をブロック、スレッドは待機状態になる。 ✤ 別のスレッドから待機状態のスレッドに起動通知 を送る ✤ 条件変数はロックを再取得し、待機状態のスレッ ドを再開させる
  • 74. std::condition_variable cond; std::mutex mutex; bool data_ready; void process_data(); void wait_for_data_to_process() { std::unique_lock<std::mutex> lock(mut); // データが準備できるまで待機する。 while(!data_ready) { cond.wait(lock); } process_data(); } condition_variable 74
  • 75. void retrieve_data(); void prepare_data(); void prepare_data_for_processing() { retrieve_data(); prepare_data(); { boost::lock_guard<boost::mutex> lock(mut); data_ready=true; } // データが準備できたら待機状態のスレッドを起こす cond.notify_one(); } condition_variable 75
  • 76. ミューテックスの型 76 ✤ std::condition_variableではミューテックスに std::unique_lock<std::mutex>を使用する。 ✤ std::condition_variable_anyでは、排他処理 に、std::unique_lock<std::mutex>以外の別のク ラスを使用できる。
  • 77. hwm.task 77
  • 78. ここまでの機能 78 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable ✤ これらを使って、タスクライブラリっぽいものを 作ってみる。
  • 79. hwm.task 79 ✤ Github : https://github.com/hotwatermorning/hwm.task.git
  • 80. hwm.task 80 ✤ Github : https://github.com/hotwatermorning/hwm.task.git ✤ git cloneして持ってきて、 SConstructに指定している Boostのパスなどを適宜書き換えて、 $> scons するとサンプルコードがビルドできる。 (※scons必要) ✤ gcc 4.8で動作確認済み。
  • 81. hwm.task 81 ✤ 概要 ✤ いくつかスレッドを起動させ、タスクキューにタスクが追 加されると、どれかのスレッドがそれを処理する。 ✤ 起動するスレッドの数はキューの初期化時に設定できる ✤ タスクはINVOKE()のように呼び出せるものであればなん でもいい。 ✤ タスクキューへのタスクの追加はスレッドセーフ ✤ タスクキューから自動でタスクが取り出され実行され、そ の実行結果は、std::futureを通じて非同期に取得できる。
  • 82. hwm.task 82 ✤ task_queue.hpp ✤ タスクキュー本体。タスクを管理し、適宜どれかのスレッ ドで取り出して実行する
  • 83. hwm.task 83 ✤ locked_queue.hpp ✤ タスクキューの実装に使用している、コンテナ。 ✤ Producer/Consumerパターンを使用したマルチスレッド セーフなキュー
  • 84. hwm.task 84 ✤ task_base.hpp/task_impl.hpp ✤ タスクを表すクラス。 ✤ run()メンバ関数によって実行される。 ✤ 実際の処理を行う部分はType Erasureというイディオム によって、baseクラスに隠 される。
  • 85. //! タスクキューで扱うタスクを表すベースクラス namespace hwm { namespace detail { namespace ns_task { struct task_base { virtual ~task_base() {} virtual void run() = 0; }; }}} task_base 85
  • 86. //! タスクの実体クラス //! Boost.Preprocessorを用いて、10引数を取るタスクまでをサポート template<class F BOOST_PP_ENUM_TRAILING(11, HWM_TASK_template_parameters, unused)> struct task_impl; #define BOOST_PP_LOCAL_MACRO(iteration_value) template<class F BOOST_PP_ENUM_TRAILING(iteration_value, HWM_TASK_template_parameters_specialized, unused)> struct task_impl<F BOOST_PP_ENUM_TRAILING_PARAMS(iteration_value, Arg) /*BOOST_PP_ENUM_TRAILING(BOOST_PP_SUB(10, iteration_value), HWM_TASK_default_params, unused) */> : task_base { typedef typename function_result_type<F BOOST_PP_ENUM_TRAILING_PARAMS(iteration_value, Arg)>::type result_type; typedef std::promise<result_type> promise_type; task_impl(promise_type && promise, F && f BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(iteration_value, Arg, &&arg)) : promise_(boost::move(promise)) , f_(std::forward<F>(f)) BOOST_PP_ENUM_TRAILING(iteration_value, HWM_TASK_initialize_member_variables, unused) {} private: task_impl(task_impl const &) = delete; task_impl & operator=(task_impl const &) = delete; promise_type promise_; F f_; BOOST_PP_REPEAT(iteration_value, HWM_TASK_define_member_variables, unused) virtual void run() override final { try { promise_.set_value(f_(BOOST_PP_ENUM(iteration_value, HWM_TASK_apply_member_variables, unused))); } catch(...) { promise_.set_exception(std::current_exception()); } } }; /**/ task_impl 86
  • 87. //! タスクの実体クラス //! Boost.Preprocessorを用いて、10引数を取るタスクまでをサポート template<class F> struct task_impl { task_imp(std::promise<F()の戻り値> &&promise, F &&f) : promise_(std::move(promise)) , f_(std::forward<F>(f)) {} std::promise<F()の戻り値> promise_; F f_; void run() override final { try { promise_.set_value(f_()); } catch(...) { promise_.set_exception(std::current_exception()); } } }; task_impl 87
  • 88. //! タスクの実体クラス //! Boost.Preprocessorを用いて、10引数を取るタスクまでをサポート template<class F, class Arg0> struct task_impl { task_imp(std::promise<F()の戻り値> &&promise, F &&f, Arg0 &&arg0) : promise_(std::move(promise)) , f_(std::forward<F>(f)) , arg0_(arg0) {} std::promise<F()の戻り値> promise_; F f_; Arg0 arg0_; void run() override final { try { promise_.set_value(f_(std::forward<Arg0>(arg0_))); } catch(...) { promise_.set_exception(std::current_exception()); } } }; task_impl 88
  • 89. example 89
  • 90. namespace hwm { struct task_queue { // タスクをキューに追加する template<class F, class... Args> std::future<F(Args)の戻り値の型> > enqueue_sync(F f, Args... args); //... }; } タスクの追加 90
  • 91. hwm::task_queue tq(スレッド数); std::future<int> f = tq.enqueue_sync( [](int x1, int x2) -> int { std::cout << (x1 + x2) << std::endl; return x1 + x2; }, 10, 20 ); // タスクキューによって自動的に非同期に実行される タスクの追加 91
  • 92. // タスクキューに積まれた処理の結果は、 // タスク追加時に返されたfutureオブジェクトを使用して // 取得する std::cout << "calculated value : " << f.get() << std::end; 実行結果の取得 92
  • 93. まとめ ✤ C++11から、言語規格でスレッドがサポートさ れました。 ✤ スレッドを扱うためのライブラリも言語に追加さ れました。 ✤ これでマルチスレッドライブラリ/アプリケー ションを書くのが容易になります。 93
  • 94. まとめ ✤並行プログラミングを極めて タダ飯の時代を! 94

×