【C++11同時プログラミングチュートリアル-Part 1:threadプローブ(bill訳)】


C++11同時プログラミングチュートリアル-Part 1:threadプローブ
注:文の中で通用する用語や行話があれば、翻訳しません.訳文に不適切な点があればご指摘ください.
原文:C++11 Concurrency-Part 1:Start Threads
C++11は新しいスレッドライブラリを導入し,スレッドを起動・管理するための多くのツールを含むとともに,反発量,ロック,原子量などを含む同期機構を提供する.このシリーズのチュートリアルでは、この新しいライブラリが提供するほとんどの特性を示してみます.
本明細書のサンプルコードをコンパイルするには、GCC 4を使用するC++11をサポートするコンパイラが必要です.6.1(「-std=c++11」または「-std=c++0 x」コンパイルオプションを追加してC++11のGCCサポートを開始する必要があります)[注:billのコンパイル環境はGCC 4.6.3+codeblocks 10.05+Ubuntu 12.04で、使用するコンパイルオプションは「-std=gnu++0 x」です.
スレッドの開始
新しいスレッドを起動するのは簡単です.std::threadのインスタンスを作成すると、自動的に起動します.スレッドインスタンスを作成するには、スレッドが実行する関数を指定する必要があります.1つの方法は、関数ポインタを渡すことです.古典的な「Hello world」で説明します.
#include <thread>
#include <iostream>
void hello(){
    std::cout << "Hello from thread " << std::endl;
}
int main(){
    std::thread t1(hello);
    t1.join();
    return 0;
}

すべてのスレッドツールは、ヘッダファイルに配置されます.この例で注目すべきは、関数join()の呼び出しである.この呼び出しは、現在のスレッドがjoinされるスレッドの終了を待つ(この例では、スレッドmainはスレッドt 1の終了を待つ必要がある後、実行を継続することができる).join()の呼び出しを無視すると、その結果は定義されていません.プログラムは「Hello from thread」と改行を印刷するか、「Hello from thread」だけを印刷して改行せず、何もしない可能性があります.それは、スレッドmainがオンラインスレッドt 1が終わる前に戻ってくる可能性があるからです.
スレッドの区別
各スレッドには、区別するために一意のIDがあります.std::threadクラスのget_を使用するid()は、対応するスレッドを識別する一意のIDを取得することができる.std::this_threadを使用して、現在のスレッドの参照を取得します.次の例では、スレッドを作成し、独自のIDを印刷します.
#include <thread>
#include <iostream>
#include <vector>
void hello(){
    std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
}
int main(){
    std::vector<std::thread> threads;
    for(int i = 0; i < 5; ++i){
        threads.push_back(std::thread(hello));
    }
    for(auto& thread : threads){
        thread.join();
    }
    return 0;
}

スレッドを順次起動してvectorに格納するのは、複数のスレッドを管理する一般的なテクニックです.これにより、スレッドの数を簡単に変更できます.本題に戻ると,上記のような短い簡単な例でも,その出力結果を断定することはできない.理論的な状況は次のとおりです.
Hello from thread 140276650997504
Hello from thread 140276667782912
Hello from thread 140276659390208
Hello from thread 140276642604800
Hello from thread 140276676175616

しかし、実際には(少なくとも私のところでは)このような状況はよくありません.あなたは次のような結果を得る可能性があります.
Hello from thread Hello from thread Hello from thread 139810974787328Hello from thread 139810983180032Hello from thread
139810966394624
139810991572736
139810958001920

あるいは他の結果.これは、スレッド間にinterleavingが存在するためです.スレッドの実行順序を制御することはできません.あるスレッドはいつでもプリエンプトされる可能性があります.また、ostreamに出力するにはいくつかのステップ(まずstringを出力し、IDを出力し、最後に改行を出力する)に分かれているため、1つのスレッドは最初のステップを実行した後、他のすべてのスレッドが印刷されてから後のステップを行うことができます.
Lambda式を使用してスレッドを開始
スレッドが実行するコードが非常に短い場合、Lambda式を使用する代わりに、関数を作成する必要はありません.上記の例をLambda式を使用する形式に簡単に書き換えることができます.
#include <thread>
#include <iostream>
#include <vector>
int main(){
    std::vector<std::thread> threads;
    for(int i = 0; i < 5; ++i){
        threads.push_back(std::thread([](){
            std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
        }));
    }
    for(auto& thread : threads){
        thread.join();
    }
    return 0;
}

以上のように,Lambda式を用いて元の関数ポインタを置換した.言うまでもなく,このコードは以前関数ポインタを用いたコードと全く同じ機能を実現している.
下編
このシリーズの次の記事では、ロックメカニズムを使用してコンカレントコードを保護する方法について説明します.