Skip to main content
Effective Modern C++Effective Modern C++勉強会勉強会 #8#8
2015/08/19
@simizut22
⾮非同期性が必須なら⾮非同期性が必須なら std::launch::asyncstd::launch::async を指定すを指定す
るる
item36item36
asyncasync のの 22 種類の種類の overloadoverload
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::launchstd::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 参照)
defaultdefault とと policypolicy 指定指定
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 as
when using a policy value of launch::async |launch::deferred,
implementations should defer invocation or the selection of the policy
when 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 policydefault policy の良さの良さ
terminate called after throwing an instance of 'std::system_error'
what(): Resource temporarily unavailable
中⽌止
さらに,resource のこういう error ⾒見なくてよくな
る
defaultdefault ,困ります,困ります(>_<)(>_<)
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 した結果
あれ,あれ,ss とと tt が⼀一致しているが⼀一致している!!(!!(環境でまちまち環境でまちまち))
defaultdefault ,困ります,困ります(>_<)(>_<)
s != t かわからない (∵ deferred になるかもしれない
s != u かわからない
そもそもget/wait されないかもしれない
s f が実⾏行されるスレッド
t async が呼ばれるスレッド
u get/wait を呼ぶスレッド
*再掲
defaultdefault ,困ります,困ります(>_<)(>_<)
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
defaultdefault ,困ります,困ります(>_<)(>_<)
次の 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 fix
future が 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 を作る
asyncasync でデフォルトが使える条件でデフォルトが使える条件
1. call/wait を呼ぶ thread と並列であってもなくてもよい
2. どの thread の TLS を読み書きするか問わない(またはTLS は使わな
い)
3. get/wait をどの path でも呼んでいる.
または実⾏行されなくても問題ない保証がある
4. wait_for/until を使っているならば deferred も考慮している
であれば async をdefault で実⾏行してもよ
い.
どれか⼀一つでも⽋欠けるなら default ,やめよう
async policyasync policy を使うを使う APIAPI ⽤用意する⽤用意する......
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 を(明
⽰示的に)指定する