C++同時学習ノート

9177 ワード

c++同時メモ
  • 同時経路
  • プロセスとスレッド
  • スレッド管理
  • スレッドセキュアstack
  • 同時パス
    古典的な比喩:2人のプログラマーが2つの独立したオフィスで一緒にソフトウェアプロジェクトをすると、静かに仕事をすることができ、互いに干渉しないことができ、マニュアルを手に入れることができます.しかし、直接話し合うよりも、電話や電子メール、相手のオフィスで直接交流しなければならない.また、2つのオフィスを管理するには一定の経費支出が必要であり、参考マニュアルを複数購入する必要がある.
    開発者が同じオフィスで仕事をすると、アプリケーションの設計を自由に議論したり、紙や白い板に簡単にグラフを描いたりして、設計の観点を補助的に説明したりすることができます.今では、オフィスを管理するだけで、参考資料さえあれば十分です.残念なことに、開発者は集中しにくく、リソース共有の問題もある可能性があります(例えば、「リファレンスマニュアルはどこですか?」[^1]:c++同時プログラミング
    以上では、同時実行の2つの基本的な方法、すなわち
  • マルチプロセス同時実行、(プロセスに関する概念)各プロセスには独自のリソース区分があり、マルチプロセス同時実行にはプロセス間通信が必要である.マルチプロセス間の通信は、速度が遅い場合にオーバーヘッドが大きく、マルチプロセスを開くとシステムオーバーヘッドが追加されます.しかし、プロセス間にオペレーティングシステムの保護があり、より安全になります.ROSの異なるノードはマルチプロセス同時に属し、ノード間でTCP/IP、socketを通じて通信(サブスクリプションとパブリッシュ)を行う.
  • マルチスレッド同時、複数のスレッドがプロセスのリソースを共有し、競合が発生しやすい.スレッド間の共有リソースの保護(反発ロック、スピンロック、読み書きロック)とプロセス間の通信(反発量、情報量)に特に注意してください.マルチスレッド関連のオーバーヘッドはマルチプロセスよりはるかに小さく、柔軟性と効率が向上します.

  • スレッドの状態:1)準備完了:スケジューリングに参加し、実行されるのを待つ.スケジューリングが選択されると、直ちに実行を開始する.2)実行:CPUを占有し、実行中である.3)スリープ:スケジューリングに参加しない.特定のイベントが発生するのを待つ.4)中止:すでに実行済み.回収待ちスレッドリソーススレッドのグローバルリソース:1)コード領域:これは、現在のプロセス空間内のすべての可視関数コードを意味し、各スレッドに対しても可視である2)静的ストレージ領域:グローバル変数、静的空間3)動的ストレージ領域:スタック空間スレッド内の典型的なローカルリソース:1)ローカルスタック空間:このスレッドを格納する関数呼び出しスタック、関数内部の局所変数など2)部分レジスタ変数:スレッドの次の実行コードのポインタオフセット量
    プロセスとスレッド
    夫がまとめたら、主に「unix環境高級プログラミング」を参考にします.
    スレッド管理
    主にの使い方を紹介します.
  • スレッド管理threadの構造:
  • void do_some_work();
    std::thread my_thread(do_some_work); //  do_some_work()      
    
    class background_task
    {
    public:
      void operator()() const
      {
        do_something();
        do_something_else();
      }
    };
    
    background_task f;
    std::thread my_thread(f);//            
    std::thread my_thread2{background_task()};//  c++11            。
    
  • スレッドエントリ関数にパラメータ
  • を渡す
    class background_task
    {
    public:
    	void operator()(int times, std::string& s) {
    		do_some_work_else(times, s);
    	}
    };
    std::thread my_bt{background_task(),10,str}; //       
    void f(int i, std::string const& s);
    std::thread t(f, 3, "hello");  //     
    //       ,  thread        ,                。       
    //      :
    #include
    #include
    #include
    #include
    void do_some_work_else(int times, std::string& s )
    {
    	int k = times;
    	s[2] = '0';
    	for (int i = 0; i < k; i++)
    	{
    		std::cout << s << std::endl;
    	}
    }
    class background_task
    {
    public:
    	void operator()(int times, std::string& s) {
    		do_some_work_else(times, s);
    	}
    };
    
    int main()//   
    {	
    	background_task bt;
    	std::string str_raw{ "do_in_main" };
    	std::string str_thread{ std::string{"do_in_thread"} };
    	//std::string &str = str_raw;
    	//std::string &strt = str_thread;
    	do_some_work_else(3, str_raw);
    	std::thread my_bt{background_task(),10,str_thread};//         ;          。
    	//std::thread my_thread(do_some_work);
    	my_bt.join();
    	//my_thread.detach();
    	std::cout << str_raw <
  • スレッドのjoin()とdetach()スレッドが起動したら、起動したスレッドの状態がjoin()かdetach()かを明確にします.std::threadオブジェクトが破棄される前にステータスが選択されていない場合、std::threadの構造関数でstd::terminate()終了プログラムが呼び出されます.join()状態の場合、現在のスレッドはjoin()スレッドが完了してから下に実行されるのを待つことを意味し、スレッドはjoin()に一度しかできません.detach()の場合、detach()スレッドはバックグラウンドで実行され、現在のスレッドとの関係から離れます.detach()スレッドは、通常、バックグラウンドで長時間実行されるデーモンスレッドとして使用されます.現在のスレッドがdetach()できると判断するにはどうすればいいですか?join()と同じように、使います.joinable()で判断します.比較するとdetach()を使用するとより自由になり,スレッドを起動すると,スレッドをdetach()分離することができる.join()は、join()を待ちたい位置に置くことにもっと注意する必要がありますが、異常発生などの原因でjoin()がスキップされないことを保証する必要があります.次の2つの解決策があります.
  • //          ,  join(),       join()
    int some_local_state=0;
      func my_func(some_local_state);
      std::thread t(my_func);
      try
      {
        do_something_in_current_thread();
      }
      catch(...)
      {
        t.join();  // 1
        throw;
      }
      t.join();  // 2
    // join()       ,    thread_guard    ,        。 thread_guard           。    guard_lock。
    //                 ,thread          , unique_ptr,    move      ,     。
    class thread_guard
    {
      std::thread t;
    public:
      explicit thread_guard(std::thread& t_):t(t_)//            。
      {}
      ~thread_guard()
      {
        if(t.joinable()) // join()      ,        
        {
          t.join();      // 2
        }
      }
      thread_guard(thread_guard const&)=delete;   //              。
      thread_guard& operator=(thread_guard const&)=delete;
    };
    struct func; //    
    void f()
    {
      int some_local_state=0;
      func my_func(some_local_state);
      std::thread t(my_func);
      thread_guard g(t);
      do_something_in_current_thread();//thread_guard        join()   。
    }  
    
  • スレッド所有権移行threadクラスを構築すると、このthreadクラスはこのスレッドの管理権を有する.スレッドthreadはunique_に似ていますptrは、所有権を移動でき、コピーできません.サンプルは次のとおりです:
  • void some_function();
    void some_other_function();
    std::thread t1(some_function);		// t1     , some_function     
    std::thread t2=std::move(t1);		//  t1   ,  move()       ,      ,t1  
    t1=std::thread(some_other_function);	//t1   some_other_function      。
    std::thread t3;							
    t3=std::move(t2);		// t2, some_function     t3			
    t1=std::move(t3);    // t3, some_function     t1,  t1       。std::terminate()   ,    。
    //    move  ,thread         ,           。
    

    スレッドの安全なstack
    #include
    #include
    #include
    #include
    
    struct empty_stack : std::exception
    {
    	const char* what() const throw()
    	{
    		return "empty stack!";
    	}
    };
    template
    class threadsafe_stack
    {
    private:
    	std::stack data;
    	mutable std::mutex m; //                   。
    						  //mutable             ,    const         。
    public:
    	threadsafe_stack()
    		: data(std::stack()) {}//    
    
    	threadsafe_stack(const threadsafe_stack& other) //    
    	{
    		std::lock_guard<:mutex> lock(other.m);//             ,                
    		data = other.data;
    	}
    
    	threadsafe_stack& operator =(const threadsafe_stack&) = delete;//       
    
    	void push(T new_value)
    	{
    		std::lock_guard<:mutex> lock(m);
    		data.push(new_value);
    	}
    	//          pop
    	//stl ,       top+pop     。
    	//                     ,             ,    
    	//           。
    	//   top pop   ,             。
    	//                     ,          。
    	std::shared_ptr pop()
    	{
    		std::lock_guard<:mutex> lock(m);
    		if (data.empty()) throw empty_stack();
    
    		std::shared_ptr const res(std::make_shared(data.top()));
    		data.pop();
    		return res;
    	}
    
    	void pop(T& value)
    	{
    		std::lock_guard<:mutex> lock(m);
    		if (data.empty()) throw empty_stack();
    
    		value = data.top();
    		data.pop();
    	}
    
    	bool empty() const
    	{
    		std::lock_guard<:mutex> lock(m);
    		return data.empty();
    	}
    
    };
    

    tips:2つのstackの関数swap( threadSafestack1, threadSafestack2)を交換することを考慮すると、swapstack1に対応する2つの反発量をstack2の内部でロックする必要がある.もちろん、ロックの順序性が保証されているため(同じswap関数であれば、ロックの順序は固定されている)、デッドロックの発生は大きく回避される.しかし、もっと優れた方法があります.
    void swap(threadSafeStack stack1, threadSafeStack stack2)
    {
    	std::lock(stack1.mutex, stack2.mutex)// std::lock()           
    	std::lock_guard<:mutex> lock1(stack1.mutex,  std::adopt_lock); //  stack1.mutex stack2.mutex       ,      std::adopt_lock,        。
    	std::lock_guard<:mutex> lock1(stack2.mutex,  std::adopt_lock);//     lock_guard          (      ,    )
    	sssswap( stack1, stack2);//          
    }
    

    以上のスレッドの安全なstackクラスがあり、本当に安全かどうかを簡単な実験で検証します.安全じゃないのに何が起こるんだ?
    void stackTest(threadsafe_stack& stack, volatile int& count)
    {
    	
    	while ( !stack.empty())
    	{
    		std::shared_ptr res = stack.pop();
    		std::cout << *res << std::endl;
    		count += 1;
    	}
    	std::cout << "safeTest end " << std::endl;
    }
    //
    void stackTest2(std::stack& stack, volatile int& count)
    {
    	
    	while (!stack.empty())
    	{
    		int res = stack.top();
    		stack.pop();
    		std::cout << res<< std::endl;
    		count += 1;
    	}
    	std::cout << "Test end " << std::endl;
    }
    int main()
    {
    	threadsafe_stack safe_stack;
    	std::stack stack;
    
    	for (int i = 0; i < 100; i++)
    	{
    		safe_stack.push(i);
    		stack.push(i);
    	}
    	volatile int count1 = 0;//      count     ,       。
    	volatile int count2 = 0;
    	std::thread t1(stackTest, std::ref(safe_stack), std::ref(count1));
    	std::thread t2(stackTest, std::ref(safe_stack), std::ref(count1));
    
    	std::thread ut1(stackTest2, std::ref(stack), std::ref(count2));
    	std::thread ut2(stackTest2, std::ref(stack), std::ref(count2));
    	
    	t1.join();
    	t2.join();
    	std::cout << "safeStack : " << count1 << std::endl;
    	
    	ut1.join();
    	ut2.join();
    	std::cout << "std::Stack : " << count2 << std::endl;
    }
    
    

    以上のコードは、おそらくクラッシュします.safeStackのpop回数を印刷するとstd::stackのempty()はスレッドが安全ではないため、stackに1つの要素しか残っておらず、2つのスレッドが同時にpopを行うとエラーが発生します.safeStackは毎回正しいことを確保します.また、safeStackのpopもスレッドが安全であるため、各要素が1回しか出力されず、漏れもないことがわかります(cout文は反発量で保護されていないため、出力順序は保証されません).しかしstd::stackでは、要素の重複出力と漏れが発生します.