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

94
2013/07/13 規格書で読むC++11のスレッド @hotwatermorning 1

description

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

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

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

2013/07/13

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

@hotwatermorning

1

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

発表者自己紹介✤ @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

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

本日のセッションを始める前に

✤今日のセッションはC++の規格と絡むので、手元に規格書があると便利です。

✤本日は規格書の代わりに、N3337を使用します。✤ ※PDF,割とサイズが大きいので注意

3

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

C++のスレッド

4

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

Free Lunch Is Over

5

✤タダ飯の時代は終わった✤ 2005年マイクロソフトの Software Architect である Herb Sutterの言葉

✤ CPUコアのクロック数向上が頭打ちとなり、時を待てば自ずとソフトウェアの性能が上がるという時代の終わり

✤→並行プログラミングが重要に

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

C++のスレッド

✤ 2011年に策定された新規格(通称:C++11)から、C++にマルチスレッドサポートが導入された。

✤これによって、ポータブルなコードでマルチスレッドアプリケーションをかけるようになった。

6

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

#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

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

C++03との違い

8

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

C++03までのスレッド

9

✤規格で、マルチスレッドをサポートしたメモリモデルや実行スレッドが定義されていなかった。

✤そのため、これまでC++でマルチスレッド処理をするためには、各プロセッサーのメモリモデルや各処理系のスレッドの定義に依存したコードを書く必要があった。

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

C++11からのスレッド

10

✤規格で、マルチスレッドをサポートしたメモリモデルや実行スレッドが定義された。

✤マルチスレッドための言語機能やライブラリも導入された。

✤標準機能のポータブルなコードでマルチスレッドが実現できるようになった。

✤スレッドライブラリも、最新のC++の知見をふんだんに使用しているので、洗練されている。

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

ライブラリ

11

✤ thread✤ mutex/lock✤ future/promise✤ async/packaged_task✤ condition_variable✤ call_once✤ atomic

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

言語機能

12

✤ static変数の初期化✤ thread_local変数のサポート✤スレッドを超えた例外の伝播(std::exception_ptr)✤などなど

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

本日やる内容

13

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

本日やる内容

14

✤ thread✤ mutex/lock✤ future/promise✤ condition_variable

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

本日やる内容

15

✤ thread✤ mutex/lock✤ future/promise✤ condition_variable

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

thread

✤スレッドを扱うクラス (§30.3.1)✤ “threadクラスは新たな実行スレッドを作成したり、そのスレッドの終了を待機したり、その他スレッドの状態を問い合せる操作のメカニズムを提供します。”

✤スレッドの作成やスレッドハンドルの管理を行う✤ そのためプログラマが常に明示的にスレッドの作成やハンドルの管理を意識する必要がない

✤ Not Copyable / Movable

16

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

スレッドの作成

✤ threadクラスのコンストラクタで、第一引数に関数や関数オブジェクトを渡し、第二引数以降にその関数に適用させたい引数を渡す(§30.3.1.2/3)(§20.8.2/1)

17

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

#include <iostream>#include <thread>

void foo(){ std::cout << "Hello" << std::endl;}

int main() { // 別スレッドでfoo関数を起動 std::thread th(foo);

// スレッドの終了を待機 th.join();}

スレッドの作成

18

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

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

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

DECAY_COPY()

✤引数に渡されたオブジェクトをコピーして受け渡す操作を表す。(§30.2.6)

✤ただし、rvalueなオブジェクトはそのままムーブされたり、配列は先頭要素へのポインタにするなどの変換が行われる。

✤詳しくはhttp://d.hatena.ne.jp/yohhoy/20120306/p1

20

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

INVOKE()

✤関数やメンバ関数へのポインタや関数オブジェクト(Functor)やラムダ式など、なんらか呼び出し可能なものを第1引数に取り、続く引数を適用して呼び出す操作を表す。(§20.8.2)

✤ https://sites.google.com/site/cpprefjp/reference/functional/invoke

✤ http://twitter.com/Cryolite/status/216814363221303296

21

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

// INVOKEが表す仮想的な呼び出し操作はINVOKE(f, t1, t2, ..., tN);

// 実際には以下から適切なものが呼び出される(t1.*f)(t2, ..., tN);

((*t1).*f)(t2, ..., tN);

f(t1, t2, ..., tN);

INVOKE()

22

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

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

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

int main() { std::string msg = "World!"; Foo foo;

// 関数のように呼び出せるものは // スレッドに渡せる std::thread th(foo, msg); // Hello World!

//スレッドの終了を待機 th.join();}

INVOKE()

24

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

スレッドに参照を渡す

✤スレッドの引数にオブジェクトの参照を渡そうとすると、DECAY_COPY()によって、コンパイルエラーとなる。✤ 参照型はコピーもムーブもできないため

✤そのため、DECAY_COPY()に渡す前に参照をstd::ref()やstd::cref()で包んで渡すようにする。

✤ http://d.hatena.ne.jp/yohhoy/20120306/p1

25

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

#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

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

thread

✤スレッドを扱うクラス (§30.3.1)✤ “threadクラスは新たな実行スレッドを作成したり、そのスレッドの終了を待機したり、その他スレッドの状態を問い合せる操作のメカニズムを提供します。”

✤スレッドの作成やスレッドハンドルの管理を行う✤ そのためプログラマが常に明示的にスレッドの作成やハンドルの管理を意識する必要がない

✤ Not Copyable / Movable

27

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

Copyable Type

✤ CopyConstructible要件 とCopyAssignable要件 を満たす型 (§17.6.3.1)

✤データをコピーして、オブジェクト間で同じ状態を実現できる

✤(Copyable自体はC++規格に定義された用語ではないが、上の2つの性質をまとめてCopyableという)

28

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

// CopyConstructibleT u = v;T(v); // CopyAssignableu = v;

Copyable Type

29

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

Movable Type

✤ MoveConstructible要件 とMoveAssignable要件 を満たす型 (§17.6.3.1)

✤データ(の所有権)を移動して、オブジェクト間でデータを受け渡せる

✤(Movable自体はC++規格に定義された用語ではないが、上の2つの性質をまとめてMovableという)

30

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

// MoveConstructibleT u = SomeFunctionReturnsT();T(SomeFunctionReturnsT()); // MoveAssignableu = SomeFunctionReturnsT();

Movable Type

31

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

// MoveConstructibleT u = std::move(t1);T(std::move(t2)); // MoveAssignableu = std::move(t3);

Movable Type

32

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

#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

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

thread

✤ threadクラスのデストラクタと、スレッド管理の問題

34

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

デストラクタとjoin/detach

35

✤ thread::join()はスレッドの終了を待機し、threadオブジェクトを初期化する関数

✤ thread::detach()はスレッドの管理を手放し、threadオブジェクトを初期化する関数

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

デストラクタと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

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

void worker(SomeParam p){ doSomething();}

void foo(SomeParam p){ std::thread th(worker, p); // このまま関数を抜ける // →std::terminate()で強制終了}

デストラクタとjoin/detach

37

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

デストラクタとjoin/detach

38

✤このままでは、あまりに活用しにくいように思われるが、そもそもthreadクラスはC++でスレッドを扱うための最もプリミティブな機能なので、このようになっている。

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

デストラクタと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

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

デストラクタと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

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

本日やる内容

41

✤ thread✤mutex/lock✤ future/promise✤ condition_variable

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

Mutex/Lock

42

✤排他処理を実現する仕組み

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

Mutexクラス

43

✤スレッド間で排他的に所有されるリソースを表すクラス (§30.4)✤ “ミューテックスオブジェクトは、データ競合に対する保護を容易にし、execution agents間での安全なデータ同期を可能にします。”

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

Mutexクラス

44

✤標準で定義されているMutexクラスは以下の4つ✤ std::mutex✤ std::recursive_mutex✤ std::timed_mutex✤ std::recursive_timed_mutex

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

std::mutex

45

✤再帰的ロック不可なミューテックス (§30.4.1.2.1)✤ Lockable

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

std::recursive_mutex

46

✤再帰的ロック可能なミューテックス (§30.4.1.2.2)✤ Lockable✤Window APIのMutexやCritical Sectionの挙動と同じ

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

Lockable Type

47

✤排他処理のために、標準規格のスレッドライブラリから使用されるオブジェクトに必要とされる要件を満たす型 (§30.2.5.3)

✤以下の3つのメンバ関数を持つなど✤ m.lock()✤ m.try_lock()✤ m.unlock()

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

SomeLockableType m;

// 所有権を取得m.lock();

// 所有権の取得を試行m.try_lock();

// 所有権を手放すm.unlock();

Lockable Type

48

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

std::timed_mutex

49

✤再帰的ロック不可な時間制限機能付きミューテックス

✤ロック処理を制限時間まで試行する✤ TimedLockable

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

std::recursive_timed_mutex

50

✤再帰的ロック可能な時間制限機能付きミューテックス

✤ロック処理を制限時間まで試行する✤ TimedLockable

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

TimedLockable Type

51

✤排他処理のために、標準規格のスレッドライブラリから使用されるオブジェクトに必要とされる要件を満たす型 (§30.2.5.3)

✤ LockableTypeの性質に加え、以下のメンバ関数を持つなど✤ m.try_lock_for()✤ m.try_lock_until()

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

#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

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

Lockクラス

53

✤ Lockableなオブジェクトを管理するRAIIクラス (§30.4.2)✤ ““lock”とはLockableなオブジェクトの参照を保持し、スコープを抜けるなどのデストラクト時には、そのLockableなオブジェクトをunlockするようなオブジェクトです。execution agentはlockableオブジェクト(のロック)の所有権を、例外安全のマナーに則って管理するための助けとして、この"lock"を使用できます。”

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

Lockクラス

54

✤それ自身は、排他処理のための直接的な機能は持たずに、参照として保持するMutexクラスを管理するだけ。

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

Lockクラス

55

✤標準規格で定義されているLockクラス✤ std::lock_guard<Mutex>✤ std::unique_lock<Mutex>

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

std::lock_guard<Mutex>

56

✤ミューテックスのシンプルな管理機構を実現するクラステンプレート

✤テンプレート引数Mutexは、BasicLockableでなければならない

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

BasicLockable Type

57

✤排他処理のために、標準規格のスレッドライブラリから使用されるオブジェクトに必要とされる要件を満たす型 (§30.2.5.3)

✤以下の2つのメンバ関数を持つなど✤ m.lock()✤ m.unlock()

✤前述のLockable Typeは、BasicLockable Typeの性質を含んでいる

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

SomeBasicLockableType m;

// 所有権を取得m.lock();

// 所有権を手放すm.unlock();

BasicLockable Type

58

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

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

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

Lockクラス

60

✤標準規格で定義されているLockクラス✤ std::lock_guard<Mutex>✤ std::unique_lock<Mutex>

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

std::unique_lock<Mutex>

61

✤ lock_guardよりも高級な処理ができるクラス✤ Mutexオブジェクトの再割当て、ムーブ、try_lock_*によるロックの試行

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

Lockクラス

62

✤ Lockクラスによって、プログラマが明示的にlock()/unlock()を対応付けて管理する必要がなくなる。

✤ RAIIというイディオムによって、例外安全性も高まる。✤ http://d.hatena.ne.jp/heisseswasser/20130508/1367988794

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

本日やる内容

63

✤ thread✤ mutex/lock✤ future/promise✤ condition_variable

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

future/promise

64

✤並行プログラミングのPromise/Futureパターンを実現する。

✤あるスレッドから、同じあるいは異なるスレッドで走る関数の結果を受け取るためのコンポーネント。マルチスレッドプログラミングのためだけではなく、シングルスレッドプログラムでも、非同期処理に役立つ。(§30.6.1)

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

future/promise

65

✤ std::promiseクラスとstd::futureクラスの対応するオブジェクト同士は、一つのshared stateを共有する。(§30.6.4)

✤データを作る側はpromiseに値を設定し、データを受け取る側ではfutureから値を取得する

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

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

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

std::promise<R>

67

✤ set_value(R const &)✤ 非同期処理の結果として値を設定

✤ この形式の他に、Rの型によって異なるシグネチャのものが存在する。

✤ set_exception(std::exception_ptr)✤ 非同期処理の結果として例外を設定

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

std::future<R>

68

✤ get()✤ 非同期処理の結果を取得する

✤ この形式の他に、Rの型によって戻り値やシグネチャが異なるものが存在する。✤ promiseで例外が設定された場合は、get()の呼び出しで、もとの例外が再送出される

✤ wait()✤ 非同期処理の結果がpromiseに設定されるまで処理をブロックする(値/例外はまだ取得しない)

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

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

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

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

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

本日やる内容

71

✤ thread✤ mutex/lock✤ future/promise✤ condition_variable

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

condition_variable

72

✤条件変数という、スレッド間で同期をとる仕組み (§30.5/1)✤ “条件変数は、ある条件が満たされた事によって他のスレッドから通知を受けたり、システム時間が設定時刻に到達するまでスレッドをブロックするのに使用される同期プリミティブを提供します。”

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

条件変数の動作原理

73

✤あるスレッドでミューテックスにロックをかけ、条件変数にミューテックスを渡す。

✤条件変数はミューテックスのロックを解放し、処理をブロック、スレッドは待機状態になる。

✤別のスレッドから待機状態のスレッドに起動通知を送る

✤条件変数はロックを再取得し、待機状態のスレッドを再開させる

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

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

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

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

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

ミューテックスの型

76

✤ std::condition_variableではミューテックスにstd::unique_lock<std::mutex>を使用する。

✤ std::condition_variable_anyでは、排他処理に、std::unique_lock<std::mutex>以外の別のクラスを使用できる。

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

hwm.task

77

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

ここまでの機能

78

✤ thread✤ mutex/lock✤ future/promise✤ condition_variable✤これらを使って、タスクライブラリっぽいものを作ってみる。

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

hwm.task

79

✤ Github : https://github.com/hotwatermorning/hwm.task.git

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

hwm.task

80

✤ Github : https://github.com/hotwatermorning/hwm.task.git✤ git cloneして持ってきて、SConstructに指定しているBoostのパスなどを適宜書き換えて、$> sconsするとサンプルコードがビルドできる。(※scons必要)

✤ gcc 4.8で動作確認済み。

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

hwm.task

81

✤概要✤ いくつかスレッドを起動させ、タスクキューにタスクが追加されると、どれかのスレッドがそれを処理する。

✤ 起動するスレッドの数はキューの初期化時に設定できる✤ タスクはINVOKE()のように呼び出せるものであればなんでもいい。

✤ タスクキューへのタスクの追加はスレッドセーフ✤ タスクキューから自動でタスクが取り出され実行され、その実行結果は、std::futureを通じて非同期に取得できる。

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

hwm.task

82

✤ task_queue.hpp✤ タスクキュー本体。タスクを管理し、適宜どれかのスレッドで取り出して実行する

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

hwm.task

83

✤ locked_queue.hpp✤ タスクキューの実装に使用している、コンテナ。✤ Producer/Consumerパターンを使用したマルチスレッドセーフなキュー

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

hwm.task

84

✤ task_base.hpp/task_impl.hpp✤ タスクを表すクラス。✤ run()メンバ関数によって実行される。✤ 実際の処理を行う部分はType Erasureというイディオムによって、baseクラスに隠蔽される。

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

//! タスクキューで扱うタスクを表すベースクラス

namespace hwm {namespace detail { namespace ns_task {

struct task_base{ virtual ̃task_base() {} virtual void run() = 0;};

}}}

task_base

85

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

//! タスクの実体クラス//! 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

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

//! タスクの実体クラス//! 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

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

//! タスクの実体クラス//! 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

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

example

89

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

namespace hwm {

struct task_queue {

// タスクをキューに追加する template<class F, class... Args> std::future<F(Args)の戻り値の型> > enqueue_sync(F f, Args... args); //...};

}

タスクの追加

90

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

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

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

// タスクキューに積まれた処理の結果は、// タスク追加時に返されたfutureオブジェクトを使用して// 取得するstd::cout << "calculated value : " << f.get() << std::end;

実行結果の取得

92

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

まとめ

✤ C++11から、言語規格でスレッドがサポートされました。

✤スレッドを扱うためのライブラリも言語に追加されました。

✤これでマルチスレッドライブラリ/アプリケーションを書くのが容易になります。

93

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

まとめ

✤並行プログラミングを極めてタダ飯の時代を!

94