C++のthread

 非同期処理は前々から手を付けないとなぁと思っていたのでつけ始めた。
多分競プロとかには使えない。
できたコードはこれ
なるほど。容易に自身の足を打ち抜いてしまうなぁ。

 確かに処理時間は体感でも早くなると感じたし、処理内容によっては並列化したコードに書き換えるコストを上回るほど早くなることもあるんだろうと思う。

 非同期処理をやってみるにあたって、知らなければ多いことは多い。
最低限のルールはスレッドセーフという幻想と現実 - yohhoyの日記(別館)がわかりやすいように思う。



 比較的シンプルそうなthreadから触る。
threadごとに並列に処理が進むことが期待されるため高速化できるかもしれない。
かもしれないというのは以下の理由。

なお、複数のスレッドが真に並行実行されるか否かは処理系に依存する。(たとえばDual Core環境で4つのスレッドを作成することはできるが、ハードウェアの制約から同時処理されるのは2スレッド以下に制限される。)thread - cpprefjp C++日本語リファレンス


 極端な例(並列処理ができない環境)ではいくらたくさんのthreadに分割したとしても以下と同様になってしまうのではなかろうか。 (処理を分割したそばから終わるのを待つので分割する意味がない。)

std::vector<std::thread> vt; 
for(auto&e:vt){
  vt.emplace_back([](){/*...*/});
  vt.join();
}



 ちなみに std::thread::hardware_concurrency関数でサポートされるthread並行数を取得することができるかもしれない(0が返ることもあるらしい)。



 いろいろ試しているうちに未定義動作を踏んだ。
ある程度原因を絞り込めたもののtwitterでコメントいただけなかったらかなり厳しかったと思う。
エラーの該当箇所は下。(インデントはリンク先と違う)

try{
    std::vector<std::thread> vt;

    for (ui i{}; i < size; ++i) {
        vt.emplace_back(
            [&]() { v.at(i) = fib(ull{ 35 } +i); }//!!!
        );
    }

    for (auto& e : vt)
        e.join();

}catch (const std::exception & e) {
    std::cerr << e.what() << "\n";
}



 複数のthreadすべてで重い処理をして結果をv[i] (vはvector)に記録している。
これ自体は以下に該当するので、データ競合によるエラーを引き起こさないはず。

(1') 異なる変数に対する同時アクセスは、データ競合とはなりません。
(または、異なるコンテナ要素オブジェクトへのアクセスであれば、同時に変更操作を行ってもデータ競合とはなりません。)
スレッドセーフという幻想と現実 - yohhoyの日記(別館)



  原因はラムダ式がキャプチャする変数iの寿命がforのスコープ外で終わっているので未定義動作。
 確かにそういわれてみると、飛んでくる例外がvectorのstd::out_of_rangeだったり、その時のiの値がsizeだったり、逆算して考えると少し納得する。
 スコープ外でiが評価されたとき、その値はsizeになっているのは直感的だ(何が起きてもおかしくないという前提のわりには、ちょうど++iによってforを抜ける値になっている)。
 そしてvectorのoperator[]とatでアクセスできる範囲は[0,size-1]なのでi(=size)でアクセスしてしまうとout_of_rangeが飛ぶだろう。