Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
2013/07/13
規格書で読むC++11のスレッド
@hotwatermorning
1
発表者自己紹介
✤ @hotwatermorning
✤ DTMer
✤ C++ポケットリファレンス書きました!(共著)
✤ Amazonのなか見検索に対応!
2
http://www.amazon.co.jp/C-%E3%83%9D
%E3%...
本日のセッションを始める前に
✤ 今日のセッションはC++の規格と絡むので、手元
に規格書があると便利です。
✤ 本日は規格書の代わりに、N3337を使用しま
す。
✤ ※PDF,割とサイズが大きいので注意
3
C++のスレッド
4
Free Lunch Is Over
5
✤ タダ飯の時代は終わった
✤ 2005年
マイクロソフトの Software Architect である
Herb Sutterの言葉
✤ CPUコアのクロック数向上が頭打ちとなり、時
を待てば自ずと...
C++のスレッド
✤ 2011年に策定された新規格(通称:C++11)
から、C++にマルチスレッドサポートが
導入された。
✤ これによって、ポータブルなコードで
マルチスレッドアプリケーションをかけるように
なった。
6
#include	 <iostream>
#include	 <thread>
#include	 <vector>
#include	 <functional>
int	 main()	 {
	 	 	 	 std::vector<int>	...
C++03との違い
8
C++03までのスレッド
9
✤ 規格で、マルチスレッドをサポートしたメモリモ
デルや実行スレッドが定義されていなかった。
✤ そのため、これまでC++でマルチスレッド処理を
するためには、各プロセッサーのメモリモデルや
各処理系のスレッドの定...
C++11からのスレッド
10
✤ 規格で、マルチスレッドをサポートしたメモリモ
デルや実行スレッドが定義された。
✤ マルチスレッドための言語機能やライブラリも導
入された。
✤ 標準機能のポータブルなコードでマルチスレッド
が実現できるよう...
ライブラリ
11
✤ thread
✤ mutex/lock
✤ future/promise
✤ async/packaged_task
✤ condition_variable
✤ call_once
✤ atomic
言語機能
12
✤ static変数の初期化
✤ thread_local変数のサポート
✤ スレッドを超えた例外の伝播(std::exception_ptr)
✤ などなど
本日やる内容
13
本日やる内容
14
✤ thread
✤ mutex/lock
✤ future/promise
✤ condition_variable
本日やる内容
15
✤ thread
✤ mutex/lock
✤ future/promise
✤ condition_variable
thread
✤ スレッドを扱うクラス ( 30.3.1)
✤ threadクラスは新たな実行スレッドを作成したり、その
スレッドの終了を待機したり、その他スレッドの状態を問
い合せる操作のメカニズムを提供します。
✤ スレッドの作成やスレッド...
スレッドの作成
✤ threadクラスのコンストラクタで、第一引数に関
数や関数オブジェクトを渡し、第二引数以降にそ
の関数に適用させたい引数を渡す( 30.3.1.2/3)
( 20.8.2/1)
17
#include	 <iostream>
#include	 <thread>
void	 foo(){
	 	 	 	 std::cout	 <<	 "Hello"	 <<	 std::endl;
}
int	 main()	 {
	 	 	...
template	 <class	 F,	 class	 ...Args>
explicit	 thread(F&&	 f,	 Args&&...	 args);
//	 というコンストラクタについて、
INVOKE	 (
	  DECAY_C...
DECAY_COPY()
✤ 引数に渡されたオブジェクトをコピーして受け渡
す操作を表す。( 30.2.6)
✤ ただし、rvalueなオブジェクトはそのままムーブ
されたり、配列は先頭要素へのポインタにするな
どの変換が行われる。
✤ 詳しく...
INVOKE()
✤ 関数やメンバ関数へのポインタや関数オブジェク
ト(Functor)やラムダ式など、なんらか呼び出し
可能なものを第1引数に取り、続く引数を適用し
て呼び出す操作を表す。( 20.8.2)
✤ https://sites.g...
//	 INVOKEが表す仮想的な呼び出し操作は
INVOKE(f,	 t1,	 t2,	 ...,	 tN);
//	 実際には以下から適切なものが呼び出される
(t1.*f)(t2,	 ...,	 tN);
((*t1).*f)(t2,	 ...
struct	 Foo	 {


 //	 operator()をもつクラス
	 	 	 	 //	 →	 関数オブジェクト(別名:ファンクタ)
	 	 	 	 void	 operator()	 (std::string	 msg)	 con...
int	 main()	 {
	 	 	 	 std::string	 msg	 =	 "World!";
	 	 	 	 Foo	 foo;
	 	 	 	 //	 関数のように呼び出せるものは
	 	 	 	 //	 スレッドに渡せる
	 ...
スレッドに参照を渡す
✤ スレッドの引数にオブジェクトの参照を渡そうと
すると、DECAY_COPY()によって、コンパイル
エラーとなる。
✤ 参照型はコピーもムーブもできないため
✤ そのため、DECAY_COPY()に渡す前に参照を
st...
#include	 <thread>
#include	 <iostream>
#include	 <functional>
void	 add(int	 a,	 int	 b,	 int	 &c)	 {
	 	 	 	 c	 =	 a	 +	...
thread
✤ スレッドを扱うクラス ( 30.3.1)
✤ threadクラスは新たな実行スレッドを作成したり、その
スレッドの終了を待機したり、その他スレッドの状態を問
い合せる操作のメカニズムを提供します。
✤ スレッドの作成やスレッド...
Copyable Type
✤ CopyConstructible要件 と
CopyAssignable要件 を満たす型 ( 17.6.3.1)
✤ データをコピーして、オブジェクト間で同じ状態
を実現できる
✤ (Copyable自体はC++...
//	 CopyConstructible
T	 u	 =	 v;
T(v);
	 
//	 CopyAssignable
u	 =	 v;
Copyable Type
29
Movable Type
✤ MoveConstructible要件 と
MoveAssignable要件 を満たす型 ( 17.6.3.1)
✤ データ(の所有権)を移動して、オブジェクト間
でデータを受け渡せる
✤ (Movable自体はC...
//	 MoveConstructible
T	 u	 =	 SomeFunctionReturnsT();
T(SomeFunctionReturnsT());
	 
//	 MoveAssignable
u	 =	 SomeFunction...
//	 MoveConstructible
T	 u	 =	 std::move(t1);
T(std::move(t2));
	 
//	 MoveAssignable
u	 =	 std::move(t3);
Movable Type
32
#include	 <cassert>
#include	 <thread>
#include	 <iostream>
void	 foo(int	 a,	 int	 b)	 {	 std::cout	 <<	 a	 <<	 ",	 "	 <<...
thread
✤ threadクラスのデストラクタと、スレッド管理
の問題
34
デストラクタとjoin/detach
35
✤ thread::join()はスレッドの終了を待機し、
threadオブジェクトを初期化する関数
✤ thread::detach()はスレッドの管理を手放し、
threadオブジェクトを初期化す...
デストラクタとjoin/detach
36
✤ なんらかのスレッドを管理しているthreadオブ
ジェクトが、join()もdetach()も呼び出さずにデ
ストラクトされると、std::terminate()を呼び出
して、プログラムが強制終...
void	 worker(SomeParam	 p)
{
	  doSomething();
}
void	 foo(SomeParam	 p)
{
	  std::thread	 th(worker,	 p);
	 	 	 	 
	 	 	 ...
デストラクタとjoin/detach
38
✤ このままでは、あまりに活用しにくいように思わ
れるが、そもそもthreadクラスはC++で
スレッドを扱うための最もプリミティブな機能な
ので、このようになっている。
デストラクタとjoin/detach
39
✤ マルチスレッドや非同期処理のより高級な仕組み
としてstd::asyncやstd::future/std::promiseな
どが用意されている。
✤ ただしマルチスレッドプログラミングに便利な機...
デストラクタとjoin/detach
40
✤ 明示的にjoin()/detach()しなければプログラムが
強制終了する挙動は、そのままでは例外機構との
相性が悪い。
✤ RAIIイディオムを用いて、自動でjoinを行う方法
などが以下に紹介...
本日やる内容
41
✤ thread
✤ mutex/lock
✤ future/promise
✤ condition_variable
Mutex/Lock
42
✤ 排他処理を実現する仕組み
Mutexクラス
43
✤ スレッド間で排他的に所有されるリソースを表す
クラス ( 30.4)
✤ ミューテックスオブジェクトは、データ競合に対する
保護を容易にし、execution agents間での安全なデー
タ同期を可能にします。
Mutexクラス
44
✤ 標準で定義されているMutexクラスは以下の4つ
✤ std::mutex
✤ std::recursive_mutex
✤ std::timed_mutex
✤ std::recursive_timed_mutex
std::mutex
45
✤ 再帰的ロック不可なミューテックス ( 30.4.1.2.1)
✤ Lockable
std::recursive_mutex
46
✤ 再帰的ロック可能なミューテックス ( 30.4.1.2.2)
✤ Lockable
✤ Window APIのMutexやCritical Sectionの挙動
と同じ
Lockable Type
47
✤ 排他処理のために、標準規格のスレッドライブラ
リから使用されるオブジェクトに必要とされる要
件を満たす型 ( 30.2.5.3)
✤ 以下の3つのメンバ関数を持つなど
✤ m.lock()
✤ m.try_...
SomeLockableType	 m;
//	 所有権を取得
m.lock();
//	 所有権の取得を試行
m.try_lock();
//	 所有権を手放す
m.unlock();
Lockable Type
48
std::timed_mutex
49
✤ 再帰的ロック不可な
時間制限機能付きミューテックス
✤ ロック処理を制限時間まで試行する
✤ TimedLockable
std::recursive_timed_mutex
50
✤ 再帰的ロック可能な
時間制限機能付きミューテックス
✤ ロック処理を制限時間まで試行する
✤ TimedLockable
TimedLockable Type
51
✤ 排他処理のために、標準規格のスレッドライブラ
リから使用されるオブジェクトに必要とされる要
件を満たす型 ( 30.2.5.3)
✤ LockableTypeの性質に加え、以下のメンバ関数
を持つ...
#include	 <chrono>
SomeTimedLockableType	 m;
//	 指定時間だけ所有権の取得を試行
m.try_lock_for(
	  std::chrono::seconds(3)
	 	 	 	 );
//	...
Lockクラス
53
✤ Lockableなオブジェクトを管理するRAIIクラス
( 30.4.2)
✤ lock とはLockableなオブジェクトの参照を保持し、ス
コープを抜けるなどのデストラクト時には、その
Lockableなオブジェク...
Lockクラス
54
✤ それ自身は、排他処理のための直接的な機能は持
たずに、参照として保持するMutexクラスを管理
するだけ。
Lockクラス
55
✤ 標準規格で定義されているLockクラス
✤ std::lock_guard<Mutex>
✤ std::unique_lock<Mutex>
std::lock_guard<Mutex>
56
✤ ミューテックスのシンプルな管理機構を実現する
クラステンプレート
✤ テンプレート引数Mutexは、BasicLockableでな
ければならない
BasicLockable Type
57
✤ 排他処理のために、標準規格のスレッドライブラ
リから使用されるオブジェクトに必要とされる要
件を満たす型 ( 30.2.5.3)
✤ 以下の2つのメンバ関数を持つなど
✤ m.lock()
✤ m...
SomeBasicLockableType	 m;
//	 所有権を取得
m.lock();
//	 所有権を手放す
m.unlock();
BasicLockable Type
58
struct	 Worker	 {	 
	  //...
	 	 	 	 void	 process()	 {
	 	 	 	 	 	 	 	 for(int	 i	 =	 0;	 i	 <	 100;	 ++i)	 {
	 	 	 	 	 	...
Lockクラス
60
✤ 標準規格で定義されているLockクラス
✤ std::lock_guard<Mutex>
✤ std::unique_lock<Mutex>
std::unique_lock<Mutex>
61
✤ lock_guardよりも高級な処理ができるクラス
✤ Mutexオブジェクトの再割当て、ムーブ、try_lock_*によ
るロックの試行
Lockクラス
62
✤ Lockクラスによって、プログラマが明示的に
lock()/unlock()を対応付けて管理する必要がなく
なる。
✤ RAIIというイディオムによって、例外安全性も高
まる。
✤ http://d.hatena.ne...
本日やる内容
63
✤ thread
✤ mutex/lock
✤ future/promise
✤ condition_variable
future/promise
64
✤ 並行プログラミングのPromise/Futureパターン
を実現する。
✤ あるスレッドから、同じあるいは異なるスレッド
で走る関数の結果を受け取るためのコンポーネン
ト。マルチスレッドプログラミングのた...
future/promise
65
✤ std::promiseクラスとstd::futureクラスの対応す
るオブジェクト同士は、一つのshared stateを
共有する。( 30.6.4)
✤ データを作る側はpromiseに値を設定し、...
void	 sum_async(	 std::vector<int>	 const	 &data,
	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 std::promise<int>	 promise)	 {
	 	 	 	 i...
std::promise<R>
67
✤ set_value(R const &)
✤ 非同期処理の結果として値を設定
✤ この形式の他に、Rの型によって異なるシグネチャのものが存在する。
✤ set_exception(std::except...
std::future<R>
68
✤ get()
✤ 非同期処理の結果を取得する
✤ この形式の他に、Rの型によって戻り値やシグネチャが異なるものが存在する。
✤ promiseで例外が設定された場合は、get()の呼び出し
で、もとの例外が...
void	 sum_async(	 std::vector<int>	 const	 &data,
	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 std::promise<int>	 promise)	 {
	 	 	 	 i...
int	 main()	 {
	 	 	 	 std::vector<int>	 data	 =	 {};
	 	 	 	 std::promise<int>	 p;
	 	 	 	 std::future<int>	 f	 =	 p.get_...
本日やる内容
71
✤ thread
✤ mutex/lock
✤ future/promise
✤ condition_variable
condition_variable
72
✤ 条件変数という、スレッド間で同期をとる仕組み
( 30.5/1)
✤ 条件変数は、ある条件が満たされた事によって他のス
レッドから通知を受けたり、システム時間が設定時刻に到
達するまでスレッドをブ...
条件変数の動作原理
73
✤ あるスレッドでミューテックスにロックをかけ、
条件変数にミューテックスを渡す。
✤ 条件変数はミューテックスのロックを解放し、処
理をブロック、スレッドは待機状態になる。
✤ 別のスレッドから待機状態のスレッドに起...
std::condition_variable	 cond;
std::mutex	 mutex;
bool	 data_ready;
void	 process_data();
void	 wait_for_data_to_process()...
void	 retrieve_data();
void	 prepare_data();
void	 prepare_data_for_processing()	 {
	 	 	 	 retrieve_data();
	 	 	 	 prepa...
ミューテックスの型
76
✤ std::condition_variableではミューテックスに
std::unique_lock<std::mutex>を使用する。
✤ std::condition_variable_anyでは、排他処理
に...
hwm.task
77
ここまでの機能
78
✤ thread
✤ mutex/lock
✤ future/promise
✤ condition_variable
✤ これらを使って、タスクライブラリっぽいものを
作ってみる。
hwm.task
79
✤ Github :
https://github.com/hotwatermorning/hwm.task.git
hwm.task
80
✤ Github :
https://github.com/hotwatermorning/hwm.task.git
✤ git cloneして持ってきて、
SConstructに指定している
Boostのパスなどを適宜...
hwm.task
81
✤ 概要
✤ いくつかスレッドを起動させ、タスクキューにタスクが追
加されると、どれかのスレッドがそれを処理する。
✤ 起動するスレッドの数はキューの初期化時に設定できる
✤ タスクはINVOKE()のように呼び出せるも...
hwm.task
82
✤ task_queue.hpp
✤ タスクキュー本体。タスクを管理し、適宜どれかのスレッ
ドで取り出して実行する
hwm.task
83
✤ locked_queue.hpp
✤ タスクキューの実装に使用している、コンテナ。
✤ Producer/Consumerパターンを使用したマルチスレッド
セーフなキュー
hwm.task
84
✤ task_base.hpp/task_impl.hpp
✤ タスクを表すクラス。
✤ run()メンバ関数によって実行される。
✤ 実際の処理を行う部分はType Erasureというイディオム
によって、baseク...
//!	 タスクキューで扱うタスクを表すベースクラス
namespace	 hwm	 {
namespace	 detail	 {	 namespace	 ns_task	 {
struct	 task_base
{
	 	 	 	 virtu...
//!	 タスクの実体クラス
//!	 Boost.Preprocessorを用いて、10引数を取るタスクまでをサポート
template<class	 F	 BOOST_PP_ENUM_TRAILING(11,	 HWM_TASK_templ...
//!	 タスクの実体クラス
//!	 Boost.Preprocessorを用いて、10引数を取るタスクまでをサポート
template<class	 F>
struct	 task_impl	 {
	 	 	 	 task_imp(std:...
//!	 タスクの実体クラス
//!	 Boost.Preprocessorを用いて、10引数を取るタスクまでをサポート
template<class	 F,	 class	 Arg0>
struct	 task_impl	 {
	 	 	 	...
example
89
namespace	 hwm	 {
struct	 task_queue	 {


 //	 タスクをキューに追加する
	  template<class	 F,	 class...	 Args>


 

 	 	 std::future<F...
hwm::task_queue	 tq(スレッド数);
std::future<int>	 f	 =
	  tq.enqueue_sync(
	 	 	 	 	  [](int	 x1,	 int	 x2)	 ->	 int	 {
	 	 	 ...
//	 タスクキューに積まれた処理の結果は、
//	 タスク追加時に返されたfutureオブジェクトを使用して
//	 取得する
std::cout
	 	 	 	 <<	 "calculated	 value	 :	 "	 <<	 f.get...
まとめ
✤ C++11から、言語規格でスレッドがサポートさ
れました。
✤ スレッドを扱うためのライブラリも言語に追加さ
れました。
✤ これでマルチスレッドライブラリ/アプリケー
ションを書くのが容易になります。
93
まとめ
✤並行プログラミングを極めて
タダ飯の時代を!
94
Upcoming SlideShare
Loading in …5
×

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

12,211 views

Published on

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

Published in: Technology
  • Be the first to comment

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

  1. 1. 2013/07/13 規格書で読むC++11のスレッド @hotwatermorning 1
  2. 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. 3. 本日のセッションを始める前に ✤ 今日のセッションはC++の規格と絡むので、手元 に規格書があると便利です。 ✤ 本日は規格書の代わりに、N3337を使用しま す。 ✤ ※PDF,割とサイズが大きいので注意 3
  4. 4. C++のスレッド 4
  5. 5. Free Lunch Is Over 5 ✤ タダ飯の時代は終わった ✤ 2005年 マイクロソフトの Software Architect である Herb Sutterの言葉 ✤ CPUコアのクロック数向上が頭打ちとなり、時 を待てば自ずとソフトウェアの性能が上がるとい う時代の終わり ✤ →並行プログラミングが重要に
  6. 6. C++のスレッド ✤ 2011年に策定された新規格(通称:C++11) から、C++にマルチスレッドサポートが 導入された。 ✤ これによって、ポータブルなコードで マルチスレッドアプリケーションをかけるように なった。 6
  7. 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. 8. C++03との違い 8
  9. 9. C++03までのスレッド 9 ✤ 規格で、マルチスレッドをサポートしたメモリモ デルや実行スレッドが定義されていなかった。 ✤ そのため、これまでC++でマルチスレッド処理を するためには、各プロセッサーのメモリモデルや 各処理系のスレッドの定義に依存したコードを書 く必要があった。
  10. 10. C++11からのスレッド 10 ✤ 規格で、マルチスレッドをサポートしたメモリモ デルや実行スレッドが定義された。 ✤ マルチスレッドための言語機能やライブラリも導 入された。 ✤ 標準機能のポータブルなコードでマルチスレッド が実現できるようになった。 ✤ スレッドライブラリも、最新のC++の知見をふん だんに使用しているので、洗練されている。
  11. 11. ライブラリ 11 ✤ thread ✤ mutex/lock ✤ future/promise ✤ async/packaged_task ✤ condition_variable ✤ call_once ✤ atomic
  12. 12. 言語機能 12 ✤ static変数の初期化 ✤ thread_local変数のサポート ✤ スレッドを超えた例外の伝播(std::exception_ptr) ✤ などなど
  13. 13. 本日やる内容 13
  14. 14. 本日やる内容 14 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  15. 15. 本日やる内容 15 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  16. 16. thread ✤ スレッドを扱うクラス ( 30.3.1) ✤ threadクラスは新たな実行スレッドを作成したり、その スレッドの終了を待機したり、その他スレッドの状態を問 い合せる操作のメカニズムを提供します。 ✤ スレッドの作成やスレッドハンドルの管理を行う ✤ そのためプログラマが常に明示的にスレッドの作成や ハンドルの管理を意識する必要がない ✤ Not Copyable / Movable 16
  17. 17. スレッドの作成 ✤ threadクラスのコンストラクタで、第一引数に関 数や関数オブジェクトを渡し、第二引数以降にそ の関数に適用させたい引数を渡す( 30.3.1.2/3) ( 20.8.2/1) 17
  18. 18. #include <iostream> #include <thread> void foo(){ std::cout << "Hello" << std::endl; } int main() { // 別スレッドでfoo関数を起動 std::thread th(foo); // スレッドの終了を待機 th.join(); } スレッドの作成 18
  19. 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. 20. DECAY_COPY() ✤ 引数に渡されたオブジェクトをコピーして受け渡 す操作を表す。( 30.2.6) ✤ ただし、rvalueなオブジェクトはそのままムーブ されたり、配列は先頭要素へのポインタにするな どの変換が行われる。 ✤ 詳しくは http://d.hatena.ne.jp/yohhoy/20120306/p1 20
  21. 21. INVOKE() ✤ 関数やメンバ関数へのポインタや関数オブジェク ト(Functor)やラムダ式など、なんらか呼び出し 可能なものを第1引数に取り、続く引数を適用し て呼び出す操作を表す。( 20.8.2) ✤ https://sites.google.com/site/cpprefjp/reference/functional/invoke ✤ http://twitter.com/Cryolite/status/216814363221303296 21
  22. 22. // INVOKEが表す仮想的な呼び出し操作は INVOKE(f, t1, t2, ..., tN); // 実際には以下から適切なものが呼び出される (t1.*f)(t2, ..., tN); ((*t1).*f)(t2, ..., tN); f(t1, t2, ..., tN); INVOKE() 22
  23. 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. 24. int main() { std::string msg = "World!"; Foo foo; // 関数のように呼び出せるものは // スレッドに渡せる std::thread th(foo, msg); // Hello World! //スレッドの終了を待機 th.join(); } INVOKE() 24
  25. 25. スレッドに参照を渡す ✤ スレッドの引数にオブジェクトの参照を渡そうと すると、DECAY_COPY()によって、コンパイル エラーとなる。 ✤ 参照型はコピーもムーブもできないため ✤ そのため、DECAY_COPY()に渡す前に参照を std::ref()やstd::cref()で包んで渡すようにする。 ✤ http://d.hatena.ne.jp/yohhoy/20120306/p1 25
  26. 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. 27. thread ✤ スレッドを扱うクラス ( 30.3.1) ✤ threadクラスは新たな実行スレッドを作成したり、その スレッドの終了を待機したり、その他スレッドの状態を問 い合せる操作のメカニズムを提供します。 ✤ スレッドの作成やスレッドハンドルの管理を行う ✤ そのためプログラマが常に明示的にスレッドの作成や ハンドルの管理を意識する必要がない ✤ Not Copyable / Movable 27
  28. 28. Copyable Type ✤ CopyConstructible要件 と CopyAssignable要件 を満たす型 ( 17.6.3.1) ✤ データをコピーして、オブジェクト間で同じ状態 を実現できる ✤ (Copyable自体はC++規格に定義された用語で はないが、上の2つの性質をまとめてCopyable という) 28
  29. 29. // CopyConstructible T u = v; T(v); // CopyAssignable u = v; Copyable Type 29
  30. 30. Movable Type ✤ MoveConstructible要件 と MoveAssignable要件 を満たす型 ( 17.6.3.1) ✤ データ(の所有権)を移動して、オブジェクト間 でデータを受け渡せる ✤ (Movable自体はC++規格に定義された用語で はないが、上の2つの性質をまとめてMovableと いう) 30
  31. 31. // MoveConstructible T u = SomeFunctionReturnsT(); T(SomeFunctionReturnsT()); // MoveAssignable u = SomeFunctionReturnsT(); Movable Type 31
  32. 32. // MoveConstructible T u = std::move(t1); T(std::move(t2)); // MoveAssignable u = std::move(t3); Movable Type 32
  33. 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. 34. thread ✤ threadクラスのデストラクタと、スレッド管理 の問題 34
  35. 35. デストラクタとjoin/detach 35 ✤ thread::join()はスレッドの終了を待機し、 threadオブジェクトを初期化する関数 ✤ thread::detach()はスレッドの管理を手放し、 threadオブジェクトを初期化する関数
  36. 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. 37. void worker(SomeParam p) { doSomething(); } void foo(SomeParam p) { std::thread th(worker, p); // このまま関数を抜ける // →std::terminate()で強制終了 } デストラクタとjoin/detach 37
  38. 38. デストラクタとjoin/detach 38 ✤ このままでは、あまりに活用しにくいように思わ れるが、そもそもthreadクラスはC++で スレッドを扱うための最もプリミティブな機能な ので、このようになっている。
  39. 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. 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. 本日やる内容 41 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  42. 42. Mutex/Lock 42 ✤ 排他処理を実現する仕組み
  43. 43. Mutexクラス 43 ✤ スレッド間で排他的に所有されるリソースを表す クラス ( 30.4) ✤ ミューテックスオブジェクトは、データ競合に対する 保護を容易にし、execution agents間での安全なデー タ同期を可能にします。
  44. 44. Mutexクラス 44 ✤ 標準で定義されているMutexクラスは以下の4つ ✤ std::mutex ✤ std::recursive_mutex ✤ std::timed_mutex ✤ std::recursive_timed_mutex
  45. 45. std::mutex 45 ✤ 再帰的ロック不可なミューテックス ( 30.4.1.2.1) ✤ Lockable
  46. 46. std::recursive_mutex 46 ✤ 再帰的ロック可能なミューテックス ( 30.4.1.2.2) ✤ Lockable ✤ Window APIのMutexやCritical Sectionの挙動 と同じ
  47. 47. Lockable Type 47 ✤ 排他処理のために、標準規格のスレッドライブラ リから使用されるオブジェクトに必要とされる要 件を満たす型 ( 30.2.5.3) ✤ 以下の3つのメンバ関数を持つなど ✤ m.lock() ✤ m.try_lock() ✤ m.unlock()
  48. 48. SomeLockableType m; // 所有権を取得 m.lock(); // 所有権の取得を試行 m.try_lock(); // 所有権を手放す m.unlock(); Lockable Type 48
  49. 49. std::timed_mutex 49 ✤ 再帰的ロック不可な 時間制限機能付きミューテックス ✤ ロック処理を制限時間まで試行する ✤ TimedLockable
  50. 50. std::recursive_timed_mutex 50 ✤ 再帰的ロック可能な 時間制限機能付きミューテックス ✤ ロック処理を制限時間まで試行する ✤ TimedLockable
  51. 51. TimedLockable Type 51 ✤ 排他処理のために、標準規格のスレッドライブラ リから使用されるオブジェクトに必要とされる要 件を満たす型 ( 30.2.5.3) ✤ LockableTypeの性質に加え、以下のメンバ関数 を持つなど ✤ m.try_lock_for() ✤ m.try_lock_until()
  52. 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. 53. Lockクラス 53 ✤ Lockableなオブジェクトを管理するRAIIクラス ( 30.4.2) ✤ lock とはLockableなオブジェクトの参照を保持し、ス コープを抜けるなどのデストラクト時には、その Lockableなオブジェクトをunlockするようなオブジェク トです。 execution agentはlockableオブジェクト(のロック)の 所有権を、例外安全のマナーに則って管理するための助け として、この"lock"を使用できます。
  54. 54. Lockクラス 54 ✤ それ自身は、排他処理のための直接的な機能は持 たずに、参照として保持するMutexクラスを管理 するだけ。
  55. 55. Lockクラス 55 ✤ 標準規格で定義されているLockクラス ✤ std::lock_guard<Mutex> ✤ std::unique_lock<Mutex>
  56. 56. std::lock_guard<Mutex> 56 ✤ ミューテックスのシンプルな管理機構を実現する クラステンプレート ✤ テンプレート引数Mutexは、BasicLockableでな ければならない
  57. 57. BasicLockable Type 57 ✤ 排他処理のために、標準規格のスレッドライブラ リから使用されるオブジェクトに必要とされる要 件を満たす型 ( 30.2.5.3) ✤ 以下の2つのメンバ関数を持つなど ✤ m.lock() ✤ m.unlock() ✤ 前述のLockable Typeは、BasicLockable Type の性質を含んでいる
  58. 58. SomeBasicLockableType m; // 所有権を取得 m.lock(); // 所有権を手放す m.unlock(); BasicLockable Type 58
  59. 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. 60. Lockクラス 60 ✤ 標準規格で定義されているLockクラス ✤ std::lock_guard<Mutex> ✤ std::unique_lock<Mutex>
  61. 61. std::unique_lock<Mutex> 61 ✤ lock_guardよりも高級な処理ができるクラス ✤ Mutexオブジェクトの再割当て、ムーブ、try_lock_*によ るロックの試行
  62. 62. Lockクラス 62 ✤ Lockクラスによって、プログラマが明示的に lock()/unlock()を対応付けて管理する必要がなく なる。 ✤ RAIIというイディオムによって、例外安全性も高 まる。 ✤ http://d.hatena.ne.jp/heisseswasser/20130508/1367988794
  63. 63. 本日やる内容 63 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  64. 64. future/promise 64 ✤ 並行プログラミングのPromise/Futureパターン を実現する。 ✤ あるスレッドから、同じあるいは異なるスレッド で走る関数の結果を受け取るためのコンポーネン ト。マルチスレッドプログラミングのためだけで はなく、シングルスレッドプログラムでも、非同 期処理に役立つ。( 30.6.1)
  65. 65. future/promise 65 ✤ std::promiseクラスとstd::futureクラスの対応す るオブジェクト同士は、一つのshared stateを 共有する。( 30.6.4) ✤ データを作る側はpromiseに値を設定し、データ を受け取る側ではfutureから値を取得する
  66. 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. 67. std::promise<R> 67 ✤ set_value(R const &) ✤ 非同期処理の結果として値を設定 ✤ この形式の他に、Rの型によって異なるシグネチャのものが存在する。 ✤ set_exception(std::exception_ptr) ✤ 非同期処理の結果として例外を設定
  68. 68. std::future<R> 68 ✤ get() ✤ 非同期処理の結果を取得する ✤ この形式の他に、Rの型によって戻り値やシグネチャが異なるものが存在する。 ✤ promiseで例外が設定された場合は、get()の呼び出し で、もとの例外が再送出される ✤ wait() ✤ 非同期処理の結果がpromiseに設定されるまで処理をブ ロックする(値/例外はまだ取得しない)
  69. 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. 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. 本日やる内容 71 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable
  72. 72. condition_variable 72 ✤ 条件変数という、スレッド間で同期をとる仕組み ( 30.5/1) ✤ 条件変数は、ある条件が満たされた事によって他のス レッドから通知を受けたり、システム時間が設定時刻に到 達するまでスレッドをブロックするのに使用される同期プ リミティブを提供します。
  73. 73. 条件変数の動作原理 73 ✤ あるスレッドでミューテックスにロックをかけ、 条件変数にミューテックスを渡す。 ✤ 条件変数はミューテックスのロックを解放し、処 理をブロック、スレッドは待機状態になる。 ✤ 別のスレッドから待機状態のスレッドに起動通知 を送る ✤ 条件変数はロックを再取得し、待機状態のスレッ ドを再開させる
  74. 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. 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. ミューテックスの型 76 ✤ std::condition_variableではミューテックスに std::unique_lock<std::mutex>を使用する。 ✤ std::condition_variable_anyでは、排他処理 に、std::unique_lock<std::mutex>以外の別のク ラスを使用できる。
  77. 77. hwm.task 77
  78. 78. ここまでの機能 78 ✤ thread ✤ mutex/lock ✤ future/promise ✤ condition_variable ✤ これらを使って、タスクライブラリっぽいものを 作ってみる。
  79. 79. hwm.task 79 ✤ Github : https://github.com/hotwatermorning/hwm.task.git
  80. 80. hwm.task 80 ✤ Github : https://github.com/hotwatermorning/hwm.task.git ✤ git cloneして持ってきて、 SConstructに指定している Boostのパスなどを適宜書き換えて、 $> scons するとサンプルコードがビルドできる。 (※scons必要) ✤ gcc 4.8で動作確認済み。
  81. 81. hwm.task 81 ✤ 概要 ✤ いくつかスレッドを起動させ、タスクキューにタスクが追 加されると、どれかのスレッドがそれを処理する。 ✤ 起動するスレッドの数はキューの初期化時に設定できる ✤ タスクはINVOKE()のように呼び出せるものであればなん でもいい。 ✤ タスクキューへのタスクの追加はスレッドセーフ ✤ タスクキューから自動でタスクが取り出され実行され、そ の実行結果は、std::futureを通じて非同期に取得できる。
  82. 82. hwm.task 82 ✤ task_queue.hpp ✤ タスクキュー本体。タスクを管理し、適宜どれかのスレッ ドで取り出して実行する
  83. 83. hwm.task 83 ✤ locked_queue.hpp ✤ タスクキューの実装に使用している、コンテナ。 ✤ Producer/Consumerパターンを使用したマルチスレッド セーフなキュー
  84. 84. hwm.task 84 ✤ task_base.hpp/task_impl.hpp ✤ タスクを表すクラス。 ✤ run()メンバ関数によって実行される。 ✤ 実際の処理を行う部分はType Erasureというイディオム によって、baseクラスに隠 される。
  85. 85. //! タスクキューで扱うタスクを表すベースクラス namespace hwm { namespace detail { namespace ns_task { struct task_base { virtual ~task_base() {} virtual void run() = 0; }; }}} task_base 85
  86. 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. 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. 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. 89. example 89
  90. 90. namespace hwm { struct task_queue { // タスクをキューに追加する template<class F, class... Args> std::future<F(Args)の戻り値の型> > enqueue_sync(F f, Args... args); //... }; } タスクの追加 90
  91. 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. 92. // タスクキューに積まれた処理の結果は、 // タスク追加時に返されたfutureオブジェクトを使用して // 取得する std::cout << "calculated value : " << f.get() << std::end; 実行結果の取得 92
  93. 93. まとめ ✤ C++11から、言語規格でスレッドがサポートさ れました。 ✤ スレッドを扱うためのライブラリも言語に追加さ れました。 ✤ これでマルチスレッドライブラリ/アプリケー ションを書くのが容易になります。 93
  94. 94. まとめ ✤並行プログラミングを極めて タダ飯の時代を! 94

×