effective modern c++ chapeter36

22
Effective Modern C++ Effective Modern C++勉強会 勉強会 #8 #8 2015/08/19 @simizut22

Upload: tatsuki-shimizu

Post on 14-Apr-2017

440 views

Category:

Software


0 download

TRANSCRIPT

Effective Modern C++Effective Modern C++勉強会勉強会 #8 #82015/08/19@simizut22

⾮非同期性が必須なら⾮非同期性が必須なら std::launch::asyncstd::launch::async を指定すを指定するる

item36item36

async async のの 2 2 種類の種類の overload overload

default

task のpolicy 指定

async(F&&, Args&&...);

async(std::launch, F&&, Args&&...);

std::launchstd::launch

enum class lauch : unspecified { async = unspecified , deferred = unspecified implementation-defined };

scoped enum を⽤用いて規定されている bitmask type

拡張 policy 及びそのbitmask の実装は,実装系に許可されている

std::launch std::launch とと std::asyncstd::async

lauch::async

lauch::deferred

異なるスレッドで⾮非同期実⾏行を指定する

呼び出しスレッドと同期実⾏行を指定する

launch::deferred 指定時get /wait が呼ばれたときに task が実⾏行される実⾏行されないかもしれない(thread と違って問題ない)

注釈について注釈について

auto f = std::async( std::launch::deferred, []{ std::cout << "wait..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; });

std::shared_future< int > sf = f.share(); auto sf2 = sf;

sf.wait(); std::cout << sf2.get() << std::endl;

sf2 に対して wait/get 呼ぶ前からsf2 の参照する shared_state は結果を持っている

(shared_state に関しては item38 参照)

default default とと policy policy 指定指定

std::async( f );

std::async( std::launch::async | std::launch::deferred, f);

default の実⾏行は bit を async/deferred 両⽅方⽴立てるのと同じ意味

つまり,次の2つは全く同じ挙動をする

このときの挙動は実装依存

とある

“ Note: If this policy is specified together with other policies, such aswhen using a policy value of launch::async |launch::deferred,

implementations should defer invocation or the selection of the policywhen no more concurrency can be effectively exploited.

実装依存!!とはいえ [futures.async] の note に

> 実⾏行ポリシーを launch::async |launch::deferred と指定したときには, 並列性を効果的に利⽤用できない場合,どちらのポリシーに従うかを決めるのを遅延するか,task の実⾏行を遅延する(deferred を選択する)べき

というところ並列性が⼗十分に取れるときの動きは書かれていない(未規定??)

http://melpon.org/wandbox/permlink/PiUr8pUjjDmA7C5V

std::cout << "hardware_concurrency = " << std::thread::hardware_concurrency()<<std::endl; using namespace std::literals; std::vector< std::future< void > > fvec(100); std::generate( std::begin(fvec), std::end(fvec), []{ return std::async([]{ std::this_thread::sleep_for(1s); std::cout << std::this_thread::get_id() <<std::endl;});}); std::for_each( std::begin(fvec), std::end(fvec), [](auto&& x){ x.wait();} );

なお,gcc は 6 系で default の挙動が変わる模様??6 より前と後で,次の code の結果がかなり違う

にあるように,同期/⾮非同期的に動くかを library の実装が決めてくれる

Bartos Mileski ⽒氏のblog

default policy default policy の良さの良さ

terminate called after throwing an instance of 'std::system_error' what(): Resource temporarily unavailable 中⽌止

さらに,resource のこういう error ⾒見なくてよくなる

default default ,困ります,困ります(>_<)(>_<)

auto fut = srd::async( f ); // use default policy

s, t, u という名前に次のようにスレッドを束縛する

s f が実⾏行されるスレッドt async が呼ばれるスレッドu get/wait を呼ぶスレッド

例えば次みたいな code を考えると...

void f() { std::cout << "s: " << std::this_thread::get_id() << std::endl; // s } std::future< void > g() { std::cout << "t: " << std::this_thread::get_id() << std::endl; // t auto fut = std::async( f ); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ return fut; } int main() { std::cout << "u:" << std::this_thread::get_id() << std::endl; // u std::async( g ).get().get(); return 0; }

http://melpon.org/wandbox/permlink/a7oHldjWHTEGA1Yq

VisualStudio2015 で build した結果

あれ,あれ,s s とと t t が⼀一致しているが⼀一致している!!(!!(環境でまちまち環境でまちまち))

default default ,困ります,困ります(>_<)(>_<)

s != t かわからない (∵ deferred になるかもしれない s != u かわからない そもそもget/wait されないかもしれない

s f が実⾏行されるスレッドt async が呼ばれるスレッドu get/wait を呼ぶスレッド

*再掲

default default ,困ります,困ります(>_<)(>_<)thread-local storage(TLS) を使ってると問題出る

thread_local int i = 0;

void fun(int left, int right ) { if (left < right-1) { auto f1 = std::async( fun, left, (left + right) / 2); auto f2 = std::async( fun, (left + right)/2, right); f1.wait(); f2.wait(); } else { ++i; } }

int main() { fun(0, 10); std::cout << i << std::endl; }

どのスレッドで fun が呼ばれるかで結果が変わるhttp://melpon.org/wandbox/permlink/lcd7FOlnXfsUBH7T

default default ,困ります,困ります(>_<)(>_<)

次の code を考える

void f () { std::this_thread::sleep( 1s ); // std::chrono::seconds(1)

auto fut = std::async(f); while (fut.wait_for(100ms) // std::chrono::milliseconds(1) != std::future_status::ready) { /* something */ }

wait_for/untilwait_for/until とと future_statusfuture_status

deferred 関数をもつ future_status::deferred

準備完了(値が返せる) future_status::ready

準備中 future_status::timeout

wait_for/until の返値は shared_stateにより以下で決まる

while (fut.wait_for(100ms) != std::future_status::ready) { }

deferred と schedule されたら,いくら待っても ready にならない!!!! bug だっ

bug fixbug fixfuture が deferred かどうか直接聞く⽅方法はないwait の返値で future_status::deferred と⾔言うのはあるwait は deferred のとき何もしない

template< typename R > inline bool is_defered( std::future< R > const& f) { return f.wait_for(0s) == std::future_status::deferred; } // shared_future も まったく同じなので割愛

auto fut = std::async(f); if (is_defered(fut)) { /* hogehoge */ } else { while (fut.wait_for(100ms)) { /* fugafuga */ } }

ということなので, check を作る

async async でデフォルトが使える条件でデフォルトが使える条件1. call/wait を呼ぶ thread と並列であってもなくてもよい

2. どの thread の TLS を読み書きするか問わない(またはTLS は使わない)

3. get/wait をどの path でも呼んでいる.または実⾏行されなくても問題ない保証がある

4. wait_for/until を使っているならば deferred も考慮している

であれば async をdefault で実⾏行してもよい.

どれか⼀一つでも⽋欠けるなら default ,やめよう

async policy async policy を使うを使う API API ⽤用意する⽤用意する......

template< typename F, typename ...Args > inline std::future< typename std::result_of< F(Args...) >::type > reallyAsync( F&& f, Args&&... args) { return std::async(std::launch::async, std::forward< F >(f), std::forward< Args >(args)...); }

c++11 version1

(c++11では)間違ってないけど...C++14 だと若干型の宣⾔言が修正されている

template< typename F, typename ...Args > inline std::future< std::result_of_t< std::decay_t< F >(std::decay_t< Args >...) > > // ^^^^^^^^^^^^ ^^^^^^^^^^^^ // decay がついてる!! reallyAsync( F&& f, Args&&... args) { return std::async(std::launch::async, std::forward< F >(f), std::forward< Args >(args)...); }

愚直に書き換え愚直に書き換え

c++14 version1

返値の型書くの,ツライ(-_-;)

修正版修正版(C++14 version)(C++14 version)

template< typename F, typename ...Args > inline auto reallyAsync( F&& f, Args&&... args) { return std::async(std::launch::async, std::forward< F >(f), std::forward< Args >(args)...); }

すっきり (^^)/

* C++11 で trailing return type 使っても,やっぱりだるい...

things to rememberthings to remember

async の default policy は task の同期/⾮非同期実⾏行のどちらにもなり得る その柔軟性のために

- TLS 使⽤用時に不確かさを起こし得る(毎回違う結果とか- task が実⾏行されないかもしれない- time-out の wait に対して program の変更が必要

などの影響をもたらす 確実に⾮非同期実⾏行にしたければ std::launch::async を(明⽰示的に)指定する