C++11多线程编程(笔记)


C++11 标准库提供std::tread类用于多线程编程。

线程的创建和阻塞

线程的三种创建方式

  1. 函数名或函数指针创建线程
  2. 函数对象(仿函数)创建线程
  3. lambda表达式创建线程。

阻塞主线程:join()和detach()函数

两者区别:子线程对象调用join()函数,主线程会被阻塞,直到子线程执行完成,主线程才能接着执行。子线程对象调用detach()函数后,执行的子线程从线程对象中分离,并不会阻塞主线程的运行。

thread类的线程对象在被销毁前必须要调用join()函数或者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;
}

小结:不要在没有关联执行线程(unjoinable状态)的thread对象上调用join()或者detach()函数;不要忘记在关联了执行线程(joinable状态)的thread对象上调用join()或者detach()。
PS:如果主线程运行完了,那些被detach分离的线程也会随着主线程的销毁而被挂起,停止运行。

线程的互斥和同步

互斥:互斥量
同步:互斥量+条件变量

条件变量(condition_variable)的使用过程:

  1. 拥有条件变量的线程获取互斥量。
unique_lock<mutex> locker(mtx);
  1. 循环检查某个条件,如果条件不满足,则阻塞当前线程直到条件满足;如果条件满足则向下执。
while(!firstOK){
    
     cond.wait(locker); } 
// 当条件变量的对象调用wait()函数时,它使用互斥量锁住当前线程。
// 当前线程一直被阻塞,直到另外一个线程在同一个条件变量的对象上调用notify_one()或notify_all()函数来唤醒当前线程。
// 用while是因为如果当前条件不满足,线程就算被唤醒了,依然需要被继续阻塞等待条件满足。
  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类成员函数作为线程函数,第一个参数是函数指针,第二个参数是对象指针(在类成员函数中创建线程,即this指针) 参考博客

猜你喜欢

转载自blog.csdn.net/XindaBlack/article/details/105525148