C ++ 11マルチスレッドプログラミング(注)


C ++ 11標準ライブラリは、マルチスレッドプログラミング用のstd :: treadクラスを提供します。

スレッドの作成とブロック

スレッドを作成する3つの方法

  1. スレッドを作成するための関数名または関数ポインター
  2. 関数オブジェクト(関数)がスレッドを作成します
  3. ラムダ式はスレッドを作成します。

メインスレッドのブロック:join()およびdetach()関数

2つの違い:子スレッドオブジェクトがjoin()関数を呼び出すと、メインスレッドはブロックされ、子スレッドの実行が完了するまで、メインスレッドは実行を継続できます。子スレッドオブジェクトがdetach()関数を呼び出した後、実行された子スレッドはスレッドオブジェクトから分離され、メインスレッドの操作をブロックしません。

スレッドクラスのスレッドオブジェクトは、破棄される前に1join()つまたは複数のdetach()関数を呼び出す必要があります。この時点で、スレッドオブジェクトには関連付けられたスレッドがなく、unjoinable状態になり、デストラクタは通常どおり呼び出すことができます。現在のスレッドオブジェクトにスレッドが関連付けられている場合、つまりスレッドがjoinable状態にある場合、この時点でオブジェクトのデストラクタが呼び出され、異常な操作が発生しますterminate called without an active exception
PS:std :: treadクラスのオブジェクトはコピーできず、オブジェクトリソースの転送のみです。std :: move()を使用して実行スレッドを現在のオブジェクトから他のオブジェクトに転送すると、現在のオブジェクトもそのunjoinable状態になります。

#include<thread>
#include<iostream>

using namespace std;


void thread_function(int n) {
    
    
  for(int i = 0; i < 100; i++){
    
    
      cout << "thread function " << n << " excuting" << endl;
  }
}

void (*f)(int n); // 声明函数指针

class DisplayThread {
    
    
 public:
  void operator() (int n) {
    
    
    for (int i = 0; i < 100; i++){
    
    
      cout << "Display Thread " << n <<" Excecuting" << endl;
    }
  }
};

int main(){
    
    
    /* 线程的创建者(父线程)必须管理创建的线程(子线程),应该等到子线程完成其任务或者让子线程从自己身上脱离。*/
    // 线程创建
    f = thread_function;
    // thread threadObj(thread_function);  // 用函数名创建线程。函数名同数组名一样都是个常量,表示函数体的首地址,并不是完全意义上的函数指针。
    thread threadObj(f,1); //用函数指针创建线程
    
    DisplayThread objectFunc;
    // objectFunc(); //objectFunc()调用与thread_function()函数调用形式上完全一样
    thread threadObj2(objectFunc,2); // 函数对象创建线程(函数对象是类的实例,调用操作符()被重载)
    
    thread threadObj3([]{
    
      //lambda表达式创建线程
        for (int i = 0; i < 100; i++){
    
    
          cout << "thread lambda expression excuting" << endl;
        }   
    });
    threadObj3.join(); // block线程,直到线程执行完毕,线程对象可以被销毁(threadObj3执行完后,才会接下去执行)
    // threadObj3.detach();  // 将线程从线程对象中分离,线程对象可以被销毁(被分离的线程仍与其它的线程并行执行)
    for (int i = 0; i < 20; i++){
    
    
        cout << "Display from MainThread" << endl;
    }

    thread threadObj4; // 默认初始化对象,还未传入回调函数
    cout << "before starting, unjoinable: " << threadObj4.joinable() << '\n';//0
    threadObj4  = thread( (DisplayThread()) , 4);  // 函数对象创建线程(通过DisplayThread()创建DisplayThread类的临时对象)
    cout << "after starting, joinable: " << threadObj4.joinable() << '\n'; //1

    thread threadObj5 = std::move(threadObj4); // 线程对象不能拷贝,只能转移。move()将threadObj4对象的资源转移到threadObj5
    cout << threadObj.get_id() << endl;  // 2
    cout << threadObj2.get_id() << endl; // 3
    cout << threadObj3.get_id() << endl; // 4
    cout << "after move, joinable: " << threadObj4.joinable() << '\n'; // 0
    // cout << threadObj4.get_id() << endl;  // threadObj4对象的线程已经被转移,处于unjoinable状态
    cout << threadObj5.get_id() << endl; // 5
    cout << this_thread::get_id() << endl; //返回当前线程id, 1

    threadObj.join();   
    threadObj2.join();
    // threadObj4.join();  // unjoinable的线程对象不能调用join()或者detach()
    threadObj5.join();
    cout << "after join(), unjoinable: " << threadObj4.joinable() << '\n';//0
    cout << "Exit of Main function" << endl;
    return 0;
}

概要:実行スレッドに関連付けられていないスレッドオブジェクト(結合不可能な状態)でjoin()またはdetach()を呼び出さないでください。実行スレッドに関連付けられているスレッドオブジェクト(結合可能な状態)でjoin()またはdetach()を呼び出すことを忘れないでください。
PS:メインスレッドがなくなると、それらの切り離されたスレッドも中断され、メインスレッドが破棄されると実行が停止します。

スレッドの相互除外と同期

ミューテックス:ミューテックス
同期:ミューテックス+条件変数

条件変数(condition_variable)の使用プロセス:

  1. 条件変数を所有するスレッドがミューテックスを取得します。
unique_lock<mutex> locker(mtx);
  1. ループ内の特定の条件を確認します。条件が満たされない場合は、条件が満たされるまで現在のスレッドをブロックします。条件が満たされる場合は、下方向に実行します。
while(!firstOK){
    
     cond.wait(locker); } 
// 当条件变量的对象调用wait()函数时,它使用互斥量锁住当前线程。
// 当前线程一直被阻塞,直到另外一个线程在同一个条件变量的对象上调用notify_one()或notify_all()函数来唤醒当前线程。
// 用while是因为如果当前条件不满足,线程就算被唤醒了,依然需要被继续阻塞等待条件满足。
  1. スレッドが条件を満たして実行されたら、待機中のスレッドの1つまたはすべてを呼び出すnotify_one()notify_all()ウェイクアップします。
cond.notify_all();

同期の例:leetcode-順番に印刷する

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<functional>
using namespace std;
class Foo {
    
    
public:
    int count = 0;
    Foo():firstOK(false), secondOK(false) {
    
    
        
    }

    void first(function<void()> printFirst) {
    
    
        unique_lock<mutex> locker(mtx);  // std::unique_lock<T>为锁管理模板类,std::unique_lock对象独占mutex对象的所有权,管理mutex对象的上锁和解锁操作
        // printFirst() outputs "first". Do not change or remove this line.
        printFirst();
        firstOK = true;
        count++;
        cond.notify_all();
    }

    void second(function<void()> printSecond) {
    
    
        unique_lock<mutex> locker(mtx);
        while(!firstOK){
    
    
            cond.wait(locker);
        }
        // printSecond() outputs "second". Do not change or remove this line.
        printSecond();
        secondOK = true;
        count++;
        cond.notify_all();
    }

    void third(function<void()> printThird) {
    
    
        unique_lock<mutex> locker(mtx);
        while(!secondOK){
    
    
            cond.wait(locker);
        }
        // printThird() outputs "third". Do not change or remove this line.
        printThird();
        count++;
        cond.notify_all();
    }
private:
    mutex mtx;
    condition_variable cond;
    bool firstOK;
    bool secondOK;
};



void printFirst(){
    
    
    cout << "first" << endl;
}

void printSecond(){
    
    
    cout << "second" << endl;
}

void printThird(){
    
    
    cout << "third" << endl;
}

int main(){
    
    
    // Foo* f = new Foo();
    // thread t1(&Foo::first, f, printFirst);
    // thread t3(&Foo::third, f, printThird);
    // thread t2(&Foo::second, f, printSecond);
    Foo f;
    // Foo* ff = new Foo();
    // std::thread类成员函数作为线程函数,第一个参数是函数指针,第二个参数是对象指针(在类成员函数中创建线程,即this指针)
    // 三个线程共用一个Foo的对象,共享互斥量、条件变量等
    thread t1(&Foo::first, &f, printFirst);
    thread t3(&Foo::third, &f, printThird);
    // thread t3(&Foo::third, ff, printThird); // 不是共用一个对象会出现死锁,因为两个对象之间的互斥量,条件变量不共享
    thread t2(&Foo::second, &f, printSecond);
    t1.join();
    t2.join();
    t3.join();
    cout << f.count << endl;
    // cout << ff->count << endl;
}

PS:std :: threadクラスメンバー関数はスレッド関数として使用されます。最初のパラメーターは関数ポインターで、2番目のパラメーターはオブジェクトポインターです(クラスメンバー関数にスレッドを作成します。つまり、このポインターです)。 リファレンスブログ

おすすめ

転載: blog.csdn.net/XindaBlack/article/details/105525148