C ++マルチスレッドスレッドの使用法

1.マルチスレッド関連のクラス

C ++ 11の新しい標準では、マルチスレッドプログラミングをサポートするために5つのヘッダーファイルが導入されています。<atomic> ,<thread>,<mutex>,<condition_variable>和<future>。

<atomic>:このヘッダーは主に、std :: atomicとstd :: atomic_flagの2つのクラスを宣言します。また、CスタイルのアトミックタイプとC互換のアトミック操作関数のセットも宣言します。このヘッダーファイルは主にstd :: threadクラスを宣言し、std :: this_thread名前空間もこのヘッダーファイルに含まれています。このヘッダーファイルは主に、std :: mutexシリーズのクラス、std :: lock_guard、std :: unique_lock、およびその他のタイプと関数を含む、ミューテックスに関連するクラスを宣言します。このヘッダーファイルは主に、std :: condition_variableやstd :: condition_variable_anyなどの条件変数に関連するクラスを宣言します。このヘッダーファイルは、主に2つのプロバイダークラスstd :: promiseとstd :: package_task、2つのFutureクラスstd :: futureとstd :: shared_future、およびいくつかの関連するタイプと関数std :: async()を宣言します。関数はこのヘッダーファイルで宣言されています。
<thread>:
<mutex>:
<condition_variable>:
<future>:

 

2.スレッドの紹介

このセクションではstd::thread 、使用法を詳しく紹介ます 

std::thread<thread> ヘッダーファイルで宣言さ れている ため、使用するには ヘッダーファイルをインクルードするstd::thread 必要があり ます <thread>

<thread> ヘッダーファイルは、std :: threadスレッドクラスとstd::swap (2つのスレッドオブジェクトを交換する)補助関数を宣言し ます。さらに、名前空間  も ヘッダーファイルで宣言され ます。以下は、C ++ 11標準で定義されている ヘッダーファイルの概要です std::this_thread<thread><thread>

namespace std 
{
    #define __STDCPP_THREADS__ __cplusplus
    class thread;
    void swap(thread& x, thread& y);
    namespace this_thread 
    {
        thread::id get_id();

        void yield();

        template <class Clock, class Duration>
        void sleep_until(const chrono::time_point<Clock, Duration>& abs_time);

        template <class Rep, class Period>
        void sleep_for(const chrono::duration<Rep, Period>& rel_time);
    }        
}

<thread> メインヘッダファイル宣言 std::thread に加えて、クラスの std::this_thread 名前空間宣言 get_idyieldsleep_until および sleep_for 他の補助機能は、このセクションの意志詳細少ない std::thread と関連する機能を。

 

std::thread スレッドオブジェクトを表し、C ++ 11標準は次のように宣言します。

namespace std 
{
    class thread 
    {
        public:
            // 类型声明:
            class id;
            typedef implementation-defined native_handle_type;

            // 构造函数、拷贝构造函数和析构函数声明:
            thread() noexcept;

            template <class F, class ...Args>
             explicit thread(F&& f, Args&&... args);

            ~thread();

            thread(const thread&) = delete;
            thread(thread&&) noexcept;
            thread& operator=(const thread&) = delete;
            thread& operator=(thread&&) noexcept;

            // 成员函数声明:
            void swap(thread&) noexcept;
            bool joinable() const noexcept;
            void join();
            void detach();
            id get_id() const noexcept;
            native_handle_type native_handle();
            
            // 静态成员函数声明:
            static unsigned hardware_concurrency() noexcept;
    };
}

std::thread 主に次の3種類の関数が宣言されています:(1)コンストラクタ、コピーコンストラクタ、デストラクタ;(2)メンバー関数;(3)静的メンバー関数。さらに std::thread::id 、スレッドIDを示し、C ++ 11は次のように宣言します。

namespace std 
{
    class thread::id 
    {
    public:
        id() noexcept;
    };

    bool operator==(thread::id x, thread::id y) noexcept;
    bool operator!=(thread::id x, thread::id y) noexcept;
    bool operator<(thread::id x, thread::id y) noexcept;
    bool operator<=(thread::id x, thread::id y) noexcept;
    bool operator>(thread::id x, thread::id y) noexcept;
    bool operator>=(thread::id x, thread::id y) noexcept;

    template<class charT, class traits>
    basic_ostream<charT, traits>& operator<< (basic_ostream<charT, traits>& out, thread::id id);


    // Hash 支持
    template <class T> struct hash;
    template <> struct hash<thread::id>;
}

 

std::thread コンストラクタ

   a。デフォルトのコンストラクターは、空のスレッド実行オブジェクトを作成します。
   b。コンストラクターを初期化し、スレッドオブジェクトを作成します。スレッドオブジェクトは結合可能であり、新しく生成されたスレッドはfn関数を呼び出し、関数のパラメーターはargsで指定されます。
   c。コピーコンストラクター(無効)。これは、スレッドをコピー構築できないことを意味します。
   d。コンストラクターの移動、コンストラクターの移動。呼び出しが成功した後、xはスレッド実行オブジェクトを表しません。
注:結合可能なスレッドオブジェクトは、破棄する前に、メインスレッドで結合するか、デタッチに設定する必要があります

 

std::thread 代入演算

ムーブ代入演算(1) thread&operator =(thread && rhs)noexcept;
コピー割り当て操作[削除済み](2) thread&operator =(const thread&)=削除;

   a。ムーブ代入演算(1)、現在のオブジェクトが使用できない場合 joinableは、右辺値参照をmove 代入演算に渡す必要があり ます。現在のオブジェクトを使用できる場合は joinableterminate()を呼び出してエラーを 報告します。

   b。コピー割り当て操作(2)が無効になっているため、  オブジェクトをコピーして割り当てることはできませんstd::thread

 

join:スレッドを結合します。この関数を呼び出すと、現在のスレッドがブロックされ*this 、マークされたスレッドの実行が終了するまで 結合は戻りません。

この時点で、スレッドコンストラクターを組み合わせて、マルチスレッドの例を示すことができます。

#include<iostream>
#include<thread>
#include<chrono>
using namespace std;

void f1(int n)
{
	for (int i = 0; i < 5; ++i) 
	{
		std::cout << "Thread " << n << " executing\n";
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));//1秒
	}
}

void f2(int& n)
{
	for (int i = 0; i < 5; ++i) 
	{
		std::cout << "Thread 2 executing\n";
		++n;
		std::this_thread::sleep_for(std::chrono::milliseconds(300));//0.3秒
	}
}

int main()
{
	int n = 0;
	std::thread t1; // t1 is not a thread
	std::thread t2(f1, n + 1); // pass by value,值传递
	std::thread t3(f2, std::ref(n)); // pass by reference,引用传递
	std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
	t2.join();
	t4.join();
	std::cout << "Final value of n is " << n << '\n';
	system("pause");
}

マルチスレッドの効果を示すために、f1を1秒のサイクルで、f2を0.3秒のサイクルで意図的に遅延させました。上記の結果は不確定です。f1とf2の出力が交互になる場合があります(coutがまだ終了していないが、タイムスライスが終了し、使用する別のスレッドの順番になっている可能性があります)。出力結果の1つを次に示します。 。あなたの結果は私が違うのと同じかもしれません:

 

detach:スレッドを切り離します。スレッドの実行を個別に実行できるように、現在のスレッドオブジェクトによって表される実行インスタンスをスレッドオブジェクトから分離します。スレッドの実行が終了すると、割り当てられたリソースが解放されます。

デタッチ関数を呼び出した後:

  1. *this スレッド実行インスタンスを表すことはなくなりました。
  2. joinable()== false
  3. get_id()== std :: id()

また、エラーが発生した場合joinable() == falseまたは 、がスローされます std::system_error

 #include <iostream>
    #include <chrono>
    #include <thread>
     
    void independentThread() 
    {
        std::cout << "Starting concurrent thread.\n";
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << "Exiting concurrent thread.\n";
    }
     
    void threadCaller() 
    {
        std::cout << "Starting thread caller.\n";
        std::thread t(independentThread);
        t.detach();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Exiting thread caller.\n";
    }
     
    int main() 
    {
        threadCaller();
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }

 

joinable:スレッドを結合できるか確認してください。現在のスレッドオブジェクトがアクティブな実行スレッドを表しているかどうかを確認します。デフォルトのコンストラクタによって作成されたスレッドは結合できません。さらに、スレッドがタスクを完了したが結合されていない場合でも、スレッドはアクティブな実行スレッドと見なされるため、結合することもできます。

  #include <iostream>
  #include <thread>
  #include <chrono>
  using namespace std;
   
  void foo()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
   
  int main()
  {
      std::thread t;
      cout << "before starting, joinable: " << t.joinable() << endl; //值为0
   
      t = std::thread(foo);
      cout << "after starting, joinable: " << t.joinable() << endl; //值为1
   
      t.join();
      cout << "after join(), joinable: " << t.joinable() << endl; //值为0
  }

 

get_id:スレッドIDを取得し、タイプのstd::thread::id オブジェクトを返し ます。次の例を考えてみましょう。

  #include <iostream>
  #include <thread>
  #include <chrono>
   
  void foo()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
   
  int main()
  {
      std::thread t1(foo);
      std::thread::id t1_id = t1.get_id();
   
      std::thread t2(foo);
      std::thread::id t2_id = t2.get_id();
   
      std::cout << "t1's id: " << t1_id << '\n';
      std::cout << "t2's id: " << t2_id << '\n';
   
      t1.join();
      t2.join();
  }

メインスレッドによって分岐されたサブスレッドの場合、コンピューターが取得するサブスレッドのget_id値は76648です。スレッドが終了すると(結合後)、値は0に戻ります。スレッドがない場合(などデフォルトのコンストラクターを使用)、値は常に0です。

 

swap:スレッドを交換し、2つのスレッドオブジェクトによって表される基になるハンドルを交換します。

 #include <iostream>
  #include <thread>
  #include <chrono>
   
  void foo()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
   
  void bar()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
   
  int main()
  {
      std::thread t1(foo);
      std::thread t2(bar);
   
      std::cout << "thread 1 id: " << t1.get_id() << std::endl;
      std::cout << "thread 2 id: " << t2.get_id() << std::endl;
   
      std::swap(t1, t2);
   
      std::cout << "after std::swap(t1, t2):" << std::endl;
      std::cout << "thread 1 id: " << t1.get_id() << std::endl;
      std::cout << "thread 2 id: " << t2.get_id() << std::endl;
   
      t1.swap(t2);
   
      std::cout << "after t1.swap(t2):" << std::endl;
      std::cout << "thread 1 id: " << t1.get_id() << std::endl;
      std::cout << "thread 2 id: " << t2.get_id() << std::endl;
   
      t1.join();
      t2.join();
  }

結果は次のとおりです。

thread 1 id: 1892
thread 2 id: 2584
after std::swap(t1, t2):
thread 1 id: 2584
thread 2 id: 1892
after t1.swap(t2):
thread 1 id: 1892
thread 2 id: 2584

 

歩留まり:現在のスレッドは実行を中止し、オペレーティングシステムは実行を継続するために別のスレッドをスケジュールします。

  #include <iostream>
  #include <chrono>
  #include <thread>
   
  // "busy sleep" while suggesting that other threads run 
  // for a small amount of time
  void little_sleep(std::chrono::microseconds us)
  {
      auto start = std::chrono::high_resolution_clock::now();
      auto end = start + us;
      do
      {
          std::this_thread::yield();
      } while (std::chrono::high_resolution_clock::now() < end);
  }
   
  int main()
  {
      auto start = std::chrono::high_resolution_clock::now();
   
      little_sleep(std::chrono::microseconds(100));
   
      auto elapsed = std::chrono::high_resolution_clock::now() - start;
      std::cout << "waited for "
                << std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()
                << " microseconds\n";
  }

 

sleep_until:スレッドが再び目覚める前に、スレッドは指定された時間(時点)までスリープします。

 template< class Clock, class Duration >
  void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time );

 

sleep_for:スレッドは指定された期間スリープした後、スレッドは再びウェイクアップされます。ただし、スレッドのスケジューリングやその他の理由により、実際のスリープ時間は指定された期間より長くなる場合が sleep_duration あります。

 template< class Rep, class Period >
  void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration );

  #include <iostream>
  #include <chrono>
  #include <thread>
   
  int main()
  {
      std::cout << "Hello waiter" << std::endl;
      std::chrono::milliseconds dura( 2000 );
      std::this_thread::sleep_for( dura );
      std::cout << "Waited 2000 ms\n";
  }

結果は次のとおりです。

Hello waiter
Waited 2000 ms

 

native_handle:ネイティブハンドルを返します(std::thread 実装はオペレーティングシステムに関連しているため 、この関数はstd::thread 特定の実装に関連するスレッドハンドルを返します。 たとえば、Posix標準プラットフォーム(Unix / Linuxなど)のPthreadライブラリです)。

  #include <thread>
  #include <iostream>
  #include <chrono>
  #include <cstring>
  #include <pthread.h>
   
  std::mutex iomutex;
  void f(int num)
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
   
     sched_param sch;
     int policy; 
     pthread_getschedparam(pthread_self(), &policy, &sch);
     std::lock_guard<std::mutex> lk(iomutex);
     std::cout << "Thread " << num << " is executing at priority "
               << sch.sched_priority << '\n';
  }
   
  int main()
  {
      std::thread t1(f, 1), t2(f, 2);
   
      sched_param sch;
      int policy; 
      pthread_getschedparam(t1.native_handle(), &policy, &sch);
      sch.sched_priority = 20;
      if(pthread_setschedparam(t1.native_handle(), SCHED_FIFO, &sch))
      {
          std::cout << "Failed to setschedparam: " << std::strerror(errno) << '\n';
      }
   
      t1.join();
      t2.join();
  }

結果は次のとおりです。

Thread 2 is executing at priority 0
Thread 1 is executing at priority 20

 

hardware_concurrency [静的]:ハードウェアの同時実行機能を検出し、現在のプラットフォームのスレッド実装でサポートされている同時スレッドの数を返しますが、戻り値はシステムヒントとしてのみ使用されます。

  #include <iostream>
  #include <thread>
   
  int main() {
      unsigned int n = std::thread::hardware_concurrency();
      std::cout << n << " concurrent threads are supported.\n";
  }

 

3.古典的な例

(1)マルチスレッドアクセス、結合の役割さらに理解するために:numの値を計算し、2 00000000を取得することを期待します

#include<iostream>
#include<thread>
#include<mutex>

const int N = 100000000;
int num(0);
mutex m;

void run()
{
	for (int i = 0; i < N; i++)
	{
		//m.lock();  //当前线程加锁后,未释放前,其它线程不可访问下面语句
		num++;
		//m.unlock();
	}
}

int main()
{
	clock_t start = clock();

	thread t1(run);
    thread t2(run);

	t1.join();
	t2.join();

	clock_t end = clock();
	cout << "num=" << num << ",用时 " << end - start << " ms" << endl;
	system("pause");
	return 0;
}

上記の例では、このマシンVS2013の結果は次のとおりです(ご覧のとおり、結果は不確実です)。

明らかに、期待した結果では正しくありません。どうしたの?はい、マルチスレッドは非同期アクセスです。複数のスレッドが同じリソースにアクセスします。ある時点で、2つのスレッドが同時にnum +1操作を実行します。たとえば、スレッド t1の 現在のnum値は100です。num++が実行されると、タイムスライスが終了し、別のスレッドが終了します。  t2が 実行されます。、num ++を操作し、num == 101を取得してから別のスレッドt1 でnum ++を実行します 。このとき、num ++の値(単純にnum = num +1と理解できます)が100から加算されます。右側のnum値はその時点で100であるため、一時変数であり、エイリアスを指定できます。num ++は、次の式と同様に、アトミック操作に変換されます。

int temp = num; //ステートメント1

num = temp + 1; //ステートメント2

したがって、上記の「同時アクセス」は次のように理解できます。t1がステートメント1を実行すると、一時値は100になり、この時点でt2はnum ++を実行しますが、num == 101が取得されますが、この時点では一時値は100のままです。操作後numこれも101に等しくなります。そのため、最終結果が期待値を下回る場合があります。

理由を知って、それを解決する方法を見てみましょう?

 

方法1:ミューテックスロックを追加する

上記のコードでコメント化されたミューテックスロックを解放します

結果は次のとおりです。

結果は正しいですが、追加とロック解除に時間がかかるため、効率は高くありません。

 

方法2:スレッドを個別に実行させる

#include<iostream>
#include<thread>
#include<mutex>

const int N = 100000000;
int num(0);
mutex m;

void run()
{
	for (int i = 0; i < N; i++)
	{
		num++;
	}
}

int main()
{
	clock_t start = clock();

	thread t1(run);
    t1.join();
    
    thread t2(run);
	t2.join();

	clock_t end = clock();
	cout << "num=" << num << ",用时 " << end - start << " ms" << endl;
	system("pause");
	return 0;
}

結果を見たい:

スレッドt1がrun()を実行するとき、joinを使用すると、現在のメインスレッドがブロックされます。つまり、結合が実行されると、現在のすべてのスレッドは影響を受けずに実行を継続し(現在開始されているスレッドは継続します)、結合後のステートメントはブロックされ、後続のステートメントは次のスレッドまで再開されません。現在、結合終了を追加しています。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

おすすめ

転載: blog.csdn.net/qq_38915078/article/details/113033841