C++11同時プログラミングのstd::thread


1、std::threadの概要と共通関数
C++11の前に、windowとlinuxプラットフォームにはそれぞれマルチスレッド標準があります.C++で記述されたマルチスレッドは、特定のプラットフォームに依存することが多い.
  • Windowプラットフォームは、マルチスレッドの作成と管理のためのwin 32 apiを提供する.
  • Linuxの下にはPOSIXマルチスレッド規格があり、ThreadsまたはPthreadsライブラリが提供するAPIはクラスUnix上で実行することができる.

  • C++11の新しい規格では,この言語に正式にマルチスレッド概念を導入した.threadライブラリを使用することで、マルチスレッドを簡単に管理できます.threadライブラリは、異なるプラットフォームのマルチスレッドAPIのパッケージと見なすことができる.
    したがって、新しい標準で提供されるスレッドライブラリを使用して作成されるプログラムは、プラットフォーム間で作成されます.
    1.1提供する構造関数:
  • デフォルトコンストラクタ                                         thread() noexcept; 
  • 関数とその伝達パラメータを受け入れる構造関数      template  explicit thread(_Fn&& _Fx, _Args&&... _Ax)
  • moveコンストラクション関数                                       thread(thread&& _Other) noexcept;
  • コピーコンストラクタ                                         thread(const thread&) = delete;
  • コピー割付演算子                                     thread& operator=(const thread&) = delete;

  • コピーコンストラクション関数とコピー付与演算子が無効になっていることは、std::threadオブジェクトが他のthreadオブジェクトにコピーおよび付与できないことを意味します.
    デフォルトのコンストラクション関数は、空のthreadオブジェクトを構築しますが、スレッドは表示されません.
    パラメータを受け入れるコンストラクション関数は、joinableであるスレッドを表すオブジェクトを作成します.
    moveコンストラクション関数は、1つのthreadオブジェクトのスレッドに対する制御権限を別のthreadオブジェクトに移行すると見なすことができる.実行後、入力されたthreadオブジェクトはスレッドを表しません.
    int main()
    {
        int arg = 0;
        std::thread t1;                        // t1 is not represent a thread
        std::thread t2(func1, arg + 1);     // pass to thread by value
        std::thread t3(func2, std::ref(arg));  // pass to thread by reference
        std::thread t4(std::move(t3));         // t4 is now running func2(). t3        
        //t1.join()  Error!
        t2.join();
        //t3.join()  Error!
        t4.join();
    }

    1.2  join && detach
    作成されたスレッドの場合、join関数とdetach関数は一般的に破棄される前に呼び出されます.
    この2つの関数の呼び出しタイミングと意味,および呼び出し前後のスレッド状態の変化を明らかにすることが重要である.
  • joinは、ターゲットスレッドが実行されるまで、現在のスレッドをブロックします.
  • joinはアクティブなスレッドでのみ呼び出され、joinable()関数でチェックできます.
  • joinable()==trueは、join関数を呼び出すことができる現在のスレッドがアクティブなスレッドであることを示します.
  • デフォルトのコンストラクション関数で作成されたオブジェクトはjoinable()==falseです.
  • joinは1回しか呼び出されず、その後joinableはfalseになり、スレッドの実行が完了したことを示す.
  • ternimate()を呼び出すスレッドはjoinable()==falseでなければなりません.
  • スレッドがjoin()関数を呼び出さない場合、実行が完了してもアクティブなスレッド、すなわちjoinable()==trueであり、join()関数を呼び出すことができる.

  • detachはthreadオブジェクトとその表すスレッドを分離する.
  • はdetachがthreadオブジェクトとその表現を表すスレッドを完全に分離するように呼び出す.
  • 分離後のスレッドは制約と規制を受けず、実行が完了するまでリソースを解放し、daemonスレッドと見なすことができる.
  • が分離された後、threadオブジェクトはスレッドを示さなくなった.
  • 分離後joinable()==false、まだ実行中であっても;


  • joinインスタンス分析:
    int main() {
        thread t(tstart, "C++ 11 thread!");
        cout << t.joinable() << endl;
        if (t.joinable()) t.join();
        //t.detach(); Error
        cout << t.joinable() << endl;
        // t.join(); Error
        cout << "Main Function!" << endl;
        system("pause");
    }
    

    簡単に言えば、アクティブなスレッドだけがjoinを呼び出すことができ、呼び出しはスレッドの実行が完了することを示し、ステータスはjoinable()==falseになる.
    joinable()==trueのスレッド、すなわちアクティブなスレッドだけがjoinとdetachを呼び出すことができます.だからjoinは2回はだめだ.
    スレッドがjoinもdetachも呼び出されない場合、スレッド実行完了ステータスはjoinable()==trueのままであり、threadオブジェクトが破棄されるとterminate()が呼び出されます.
    1.3  スレッドIDの取得
    スレッドIDは1つのスレッドの識別子であり、C++標準では2つの方法でスレッドIDを取得する.
  • thread_obj.get_id();
  • std::this_thread::get_id()

  • 空のthreadオブジェクト、すなわちスレッドを表さないthread obj呼び出しget_idの戻り値は0である.
    また、スレッドがdetachまたはjoinable()==falseによって呼び出されるとget_idの戻り結果も0である.
     
    1.4 threadスレッドの交換
    上述したdetachは、threadオブジェクトとその表すスレッドを分離したり、moveを別のスレッドに分離したりすることができるほか、swapを使用して2つのthreadオブジェクトが表すスレッドを交換したりすることもできる.
    例では、2つのスレッドの交換を見てみましょう.
    int tstart(const string& tname) {
        cout << "Thread test! " << tname << endl;
        return 0;
    }
    
    int main() {
        thread t1(tstart, "C++ 11 thread_1!");
        thread t2(tstart, "C++ 11 thread_2!");
        cout << "current thread id: " << this_thread::get_id() << endl;
        cout << "before swap: "<< " thread_1 id: " << t1.get_id() << " thread_2 id: " << t2.get_id() << endl;
        t1.swap(t2);
        cout << "after swap: " << " thread_1 id: " << t1.get_id() << " thread_2 id: " << t2.get_id() << endl;
        //t.detach();
        t1.join();
        t2.join();
    }
    

    出力:
    Thread test! C++ 11 thread_1!
    Thread test! C++ 11 thread_2!
    current thread id: 39308
    before swap:  thread_1 id: 26240 thread_2 id: 37276
    after swap:  thread_1 id: 37276 thread_2 id: 26240

    実は交換の過程はthreadオブジェクトが持っている下位ハンドルを交換しただけです.
     
    2、std::threadのプロジェクトでの使用
    2.1スレッド同期
  • mutexは、スレッドの同期を保証し、異なるスレッドが同じ共有データを同時に操作することを防止するために使用される.
  • #include:ヘッダファイルは、std::mutexシリーズクラス、std::lock_を含む反発量(mutex)に関連するクラスを主に宣言します.guard, std::unique_lock、その他のタイプと関数.
  • #include
    #include
    #include
    
    using namespace std;
    
    mutex m;
    int cnt = 10;
    
    void thread1() {
        while (cnt > 5){
            m.lock();
            if (cnt > 0) {
                --cnt;
                cout << cnt << endl;
            }
            m.unlock();
        }
    }
    
    void thread2() {
        while (cnt > 0) {
            m.lock();
            if (cnt > 0) {
                cnt -= 10;
                cout << cnt << endl;
            }
            m.unlock();
        }
    }
    
    int main(int argc, char* argv[]) {
        thread th1(thread1);   //         th1,       
        thread th2(thread2);
        th1.join();
        th2.join();
        cout << "main..." << endl;
        return 0;
    }

    注意:mutexは安全ではありません.1つのスレッドがロックを解除する前に異常に終了すると、他のブロックされたスレッドは続行できません.
    lock_の使用guardは比較的安全で、役割ドメインに基づいており、自己ロックを解除することができ、オブジェクトが作成されるとm.lock()のように反発ロックが得られ、ライフサイクルが終了すると、スレッドが異常に終了したために他のスレッドに影響を与えないように自動的にプロファイルされます.
    #include
    #include
    #include
    
    using namespace std;
    
    mutex m;
    int cnt = 10;
    
    void thread1() {
        while (cnt > 5){
            lock_guard lockGuard(m);
            if (cnt > 0) {
                --cnt;
                cout << cnt << endl;
            }
        }
    }
    
    void thread2() {
        while (cnt > 0) {
            lock_guard lockGuard(m);
            if (cnt > 0) {
                cnt -= 10;
                cout << cnt << endl;
            }
        }
    }
    
    int main(int argc, char* argv[]) {
        thread th1(thread1);   //         th1,       
        thread th2(thread2);
        th1.join();
        th2.join();
        cout << "main..." << endl;
        return 0;
    }

     
    2.2 スレッドにパラメータを渡す
    #include  
    using namespace std;  
    
    void show(const char *str, const int id)  
    {  
        cout << "   " << id + 1 << " :" << str << endl;  
    }  
    
    int main()  
    {  
        thread t1(show, "hello cplusplus!", 0);  
        thread t2(show, "  ,C++!", 1);  
        thread t3(show, "hello!", 2);  
        return 0;  
    }  

    2.3 Lambda式のthreadへの応用
    #include  
    using namespace std;  
    int main()  
    {  
        auto fun = [](const char *str) {cout << str << endl; };  
        thread t1(fun, "hello world!");  
        thread t2(fun, "hello beijing!");  
        return 0;  
    }  

    出力:
    hello world!  
    hello beijing!

     
    2.4 threadの可変パラメータ
    #include  
    #include  
    using namespace std;  
    int show(const char *fun, ...)  
    {  
        va_list ap;//    
        va_start(ap, fun);//    
        vprintf(fun, ap);//    
        va_end(ap);  
        return 0;  
    }  
    int main()  
    {  
        thread t1(show, "%s    %d    %c    %f", "hello world!", 100, 'A', 3.14159);  
        return 0;  
    }  

    出力:
    hello world!    100    A    3.14159