Linuxマルチスレッドサーバプログラミング基礎C++11同時プログラミング学習

6572 ワード

前言
C++では従来バージョンではスレッドはサポートされていません.pthreadなどの使用スレッドが必要です.C++自身のスレッドを使用できれば,プログラムを統一して簡潔にすることができる.
ヘッダファイル
  • ヘッダファイルにはstd::threadクラスとstd::this_threadクラス.スレッドを管理する関数です.スレッドを実装する主なファイルです.
  • ヘッダファイルにはstd::atomicおよびstd::atomic_が含まれています.flagクラスは、原子操作を実現する主なファイルです.
  • は、反発に関連するクラスと関数を含む.
  • にはfutureクラスと関連する関数が含まれています.
  • は、条件付き変数のクラスを含む.

  • 以上がc++11のスレッド部分です.pthreadとc++threadについては多くの議論があるが、プラットフォームをまたぐc++threadにとっては、より標準的なようだ.
    この本はc++11のスレッドを学ぶことをお勧めします.https://www.gitbook.com/book/chenxiaowei/cpp_concurrency_in_action/details
    hello worldスレッド
    threadクラスの単純な認識
    #include 
    
    using namespace std;
    //                     
    void hello()
    {
        printf("%s", "hello
    "); } // thread , 。 , int main() { thread t(hello); // join t.join(); }

    joinを除いて終了を待つには、detachを使用してスレッドの終了を待たないことができます.
    struct func
    {
      int& i;
      func(int& i_) : i(i_) {}
      void operator() ()
      {
        for (unsigned j=0 ; j<1000000 ; ++j)
        {
          do_something(i);           // 1.       :    
        }
      }
    };
    
    void oops()
    {
      int some_local_state=0;
      func my_func(some_local_state);
      std::thread my_thread(my_func);
      my_thread.detach();          // 2.        
    }                              // 3.          
    

    この例では、スレッドの終了を待たない(detach()②)と決定しているので、oops()関数の実行が完了した場合③は、新しいスレッドの関数がまだ実行されている可能性があります.スレッドがまだ実行されている場合はdo_が呼び出されますsomething(i)関数①は、破棄された変数にアクセスします.単一スレッドプログラムのように、関数が完了した後もローカル変数のポインタまたは参照を保持し続けることができます.もちろん、これは決して良いアイデアではありません.このような状況が発生すると、エラーは明らかではなく、マルチスレッドがエラーしやすくなります.
    スレッドの終了を待つにはどうすればいいですか?
    スレッドを待つ必要がある場合は、関連するstd::threadインスタンスはjoin()を使用する必要があります.インベントリ2.1でmy_thread.detach()をmyに置き換えるthread.join()は、スレッドが完了した後にローカル変数が破棄されることを保証します.この場合、元のスレッドはそのライフサイクルで何もしていないため、独立したスレッドで関数を実行するのは収益が小さくなりますが、実際のプログラミングでは、元のスレッドには独自の仕事があります.複数のサブスレッドが起動して役に立つ作業を行い、スレッドの終了を待つかのいずれかです.
    join()は単純で乱暴なスレッドの完了を待つか、待たないかです.待機中のスレッドをより柔軟に制御する必要がある場合は、たとえば、スレッドが終了しているかどうかを確認したり、しばらく待つだけ(時間を超えるとタイムアウトと判定されます).これを行うには、条件変数や期待(futures)など、他のメカニズムを使用して完了する必要があります.join()の動作を呼び出し、std::threadオブジェクトが完了したスレッドに関連付けられなくなるように、スレッド関連のストレージ部分もクリーンアップします.これは、1つのスレッドに対して1回しかjoin()を使用できないことを意味する.join()が既に使用されている場合、std::threadオブジェクトは再び追加できません.joinable()を使用するとfalseが返されます.
    パラメータをスレッドに渡す
    void f(int i, std::string const& s);
    std::thread t(f, 3, "hello");
    

    コードは、f(3,「hello」)を呼び出すスレッドを作成します.なお、関数fにはstd::stringオブジェクトを2番目のパラメータとして必要とするが、ここでは文字列の文字面値、すなわちchar const*タイプが使用される.その後、スレッドのコンテキストで文字値のstd::stringオブジェクトへの変換が完了します.
    コンストラクション関数は、関数が期待するパラメータタイプを無視し、提供された変数を盲目的にコピーすることに注意してください.
    例:
    void f(int i,std::string const& s);
    void oops(int some_param)
    {
      char buffer[1024]; // 1
      sprintf(buffer, "%i",some_param);
      std::thread t(f,3,buffer); // 2
      t.detach();
    }
    

    この場合、buffer②はポインタ変数であり、ローカル変数を指し、ローカル変数はbufferを介して新しいスレッド②に渡される.さらに、関数は、文字値がstd::stringオブジェクトに変換される前にクラッシュ(oops)し、未定義の動作を引き起こす可能性が高い.また,暗黙変換に依存してワード値を関数に変換するのに期待されるstd::stringオブジェクトを求めたが,std::threadのコンストラクション関数が与えられた変数をコピーするため,所望のタイプに変換されていない文字列ワード値のみをコピーした.解決策は、std::threadコンストラクション関数に渡す前に、フォント値をstd::stringオブジェクトに変換することです.
    void f(int i,std::string const& s);
    void not_oops(int some_param)
    {
      char buffer[1024];
      sprintf(buffer,"%i",some_param);
      std::thread t(f,3,std::string(buffer));  //   std::string,      
      t.detach();
    }
    

    たとえば、2つ目は、リファレンスを渡すことが望ましいが、オブジェクト全体がコピーされているという逆の状況に遭遇することもあります.スレッドが参照伝達データ構造を更新すると、次のようなことが起こります.
    void update_data_for_widget(widget_id w,widget_data& data); // 1
    void oops_again(widget_id w)
    {
      widget_data data;
      std::thread t(update_data_for_widget,w,data); // 2
      display_status();
      t.join();
      process_widget_data(data); // 3
    }
    

    反発の使用
    C++ではstd::mutexをインスタンス化して反発量を作成し、メンバー関数lock()を呼び出してロックし、unlock()をロック解除します.ただし、実際にメンバー関数を直接呼び出すことは推奨されません.メンバー関数を呼び出すことは、各関数の出口でunlock()を呼び出すことを覚えておく必要があります.例外も含まれます.C++標準ライブラリは、相互反発量にRAII構文のテンプレートクラスstd::lock_guardは、構造時にロックされた反発量を提供し、分析時にロックを解除し、ロックされた反発量が常に正確にロックされることを保証する.次のプログラムリストでは、マルチスレッドプログラムでstd::mutexで構築されたstd::lock_を使用する方法を示します.guardインスタンスは、リストにアクセスして保護します.std::mutexとstd::lock_guardはすべてヘッダファイルに宣言されます.
    #include 
    #include 
    #include 
    
    std::list some_list;    // 1
    std::mutex some_mutex;    // 2
    
    void add_to_list(int new_value)
    {
      std::lock_guard<:mutex> guard(some_mutex);    // 3
      some_list.push_back(new_value);
    }
    
    bool list_contains(int value_to_find)
    {
      std::lock_guard<:mutex> guard(some_mutex);    // 4
      return std::find(some_list.begin(),some_list.end(),value_to_find) != some_list.end();
    }
    

    メンバー関数の1つがデータを保護するポインタまたは参照を返すと、データの保護が破壊されます.相互反発ロックによって制限されることなく、保護されたデータにアクセス(変更可能)できるポインタまたはリファレンス.反発量保護データは、インタフェースの設計に非常に慎重である必要があります.反発量が保護データへのアクセスをロックし、バックドアを残さないようにします.
    ロックの使用
    おもちゃがあると思ってみてください.このおもちゃは2つの部分からなっていて、この2つの部分を手に入れなければ、游ぶことができません.例えば、おもちゃのドラムは、ハンマーとドラムが必要です.今は2人の子供がいます.彼らはこのおもちゃが大好きです.その中の一人の子供がドラムとハンマーを手に入れたとき、思う存分遊ぶことができます.もう一人の子供が遊びたいときは、もう一人の子供が遊び終わるのを待たなければなりません.もう一度考えてみると、ドラムとハンマーは別のおもちゃ箱に入れられ、二人の子供は同じ時間にドラムを叩きたいと思っています.その後、彼らはおもちゃ箱の中にこのドラムを探しに行きました.そのうちの1つはドラムを見つけ、もう1つはドラムハンマーを見つけた.今問題が来ました.一人の子供がもう一人を先に遊ばせることを決めない限り、彼は自分の部分をもう一人の子供にあげることができます.しかし、彼らが自分のすべての部分を握りしめて与えないと、このドラムは誰も游ぶことができません.
    幸いなことに、C++標準ライブラリはこの問題を解決する方法があります.std::lock--複数(2つ以上)の反発量を一度にロックすることができ、副作用(デッドロックリスク)はありません.
    デッドロックを回避するにはどうすればいいですか?
  • ネストされたロックを回避する最初の推奨事項は、スレッドがロックを取得した場合、2番目のロックを取得しないでください.この提案を堅持できれば、スレッドごとにロックが1つしか持たないため、ロックにデッドロックは発生しません.
  • 固定順序を使用してロックを取得ハード条件で2つ以上(2つを含む)のロックを取得する必要があり、std::lock単独で操作して取得することはできません.では、各スレッドにおいて、固定された順序でそれらを取得することが望ましい(ロック).

  • 同期待機
    もしあなたが旅行していて、夜間運行の列車に乗っているとします.夜間、どのようにして正しい駅で降りますか?1つの方法は、一晩中目が覚めて、どの駅に気づいたのかということです.これにより、到着するサイトを見逃すことはありませんが、疲れてしまいます.また、スケジュールを見て、列車が目的地に着く時間を見積もって、少し早い時間にアラームを設置して、安心して寝ることができます.この方法もいいですね.降りる駅を逃していませんが、汽車が遅れたとき、早く起こされます.もちろん、目覚まし時計のバッテリーも電気が切れ、駅を通過する可能性があります.理想的な方法は、朝でも晩でも、汽車が駅に着いたとき、誰かがあなたを呼び覚ますことができればいいということです.
    C++標準ライブラリは条件変数に対して2つの実装がある:std::condition_variableとstd::condition_variable_any.両方のインプリメンテーションは、ヘッダファイルの宣言に含まれます.両方とも1つの反発量と一緒に動作する必要があります(反発量は同期のためです).前者はstd::mutexとのみ動作し、後者は最低基準を満たす反発量とともに動作し、_を加えることができる.anyの接尾辞.なぜならstd::condition_variable_anyはより汎用的であり、ボリューム、パフォーマンス、およびシステムリソースの使用から追加のオーバーヘッドが発生する可能性があるため、std::condition_variableは一般的に第一選択のタイプとして、柔軟性にハードな要求がある場合、std::condition_を考慮します.variable_any.