C++11多线程的创建,数据共享,死锁,互斥量等线程概念及示例代码

多线程:

概念:软件或者硬件上实现多个线程并发执行的技术。比如我们一遍看电视,一遍吃瓜子,这就是多个线程。那相比于多进程怎么理解呢?比如电视机在放电视,洗衣机此时也在洗衣服,这就是多进程。一般来说线程是发生在进程之中的,我们把吃瓜子和看电视都理解为发生在个人活动这个进程中的。
C++11标准的实现主要使用了一个线程类,thread。至于内部的具体实现,在这里就不做介绍。
1.尽量不使用detach方法,容易引起很多问题。。。。。。

创建多线程的三种方法

1.调用普通的函数:

void myThread(){
  cout<<"我的线程开始了"<<endl;
  cout<<"我的线程结束了"<<endl;

}
int mian(){
 thread mythread2(myThread);

}

这是最简单的方法,对于没有值传递和交换来说,该方法较为实用

2.类成员函数的传递方法

class TA{
public:
  TA(){
    cout<<"TA()构造函数被执行"<<endl;
  }
  TA(const TA &ta){
     cout<<"TA()拷贝构造函数被执行"<<endl;
  }
  //thread mythread(ta);
  void operator ()(){
    cout<<"我的线程operator ()开始了"<<endl;
    cout<<"我的线程operator ()结束了"<<endl;
  }
  //thread mythread(bind(&TA::test2,&ta)); //band
  void test2(){
    cout<<"我的线程test2()开始了"<<endl;
    cout<<"我的线程test2()结束了"<<endl;
  }
};

int main(){
thread mythread1(ta);//直接调用重载的函数
thread mythread2(bind(&TA::test2,&ta)); //band进行参数的绑定
mythread1.join();
mythread2.join();
}

类成员函数的调用一般有两种,一种是重载()直接访问类,一种是采用Band进行参数的绑定

3.lambda表达式

int main(){
 auto lambad =[]{
  cout<<"我的线程test2()开始了"<<endl;
  cout<<"我的线程test2()结束了"<<endl;
 }
 thread mythread1(lambad);
}

多线程中的参数传递

1.普通值得传递
对于普通值来说,最好传入const只读数据类型。

void myThread2(const int i){
  cout<<"i= "<<i;
  return;

}
int main(){
  int i=0;
  thread mythread1(myThread2,i);
}

2.引用得传递

void myThread2(const int i,vector<int> &p1){
  cout<<"i= "<<i;
  i=10;
  p1.push_back(10);
  p1.push_back(20);
  p1.push_back(30);
  p1.push_back(40);
  return;

}
int main(){
  int i=20;
  vector<int> p2;
  thread mythread(myThread2,i,ref(p2));
  cout<<"i= "<<i<<endl;
  for(auto itr=p2.begin();itr!=p2.end();itr++){
      cout<<(*itr)<<endl;
    }
    mythread.join();
    return 0;
}

对于普通值得传递和函数传递得方式是一样得,只会传入参数,不能改变主函中得参数,而对于引用来说,是可以改变主函数中得值得。记得在引用前加上ref

3.指针得传递采用move()方法

void myThread3(unique_ptr<int[]> ptr){

  ptr[0]=1;
  ptr[2]=2;
  ptr[3]=3;
  ptr[4]=4;
  ptr[5]=5;

}
int main(){

 unique_ptr<int[]> ptr(new int[10]);

 thread mythread2(myThread3,move(ptr));
 mythread2.join();
}

我们知道,move后得指针是把指针移动过去,在主函数中得指针已经不在指向之前那段内存,变成了NULL;

多线程得数据共享问题

1.对于只读数据,线程之间得共享是安全得

const vector<int> p1={1,2,3,4,5};
void myPrint(int num){
    std::cout<<"my sub thread id: "<<this_thread::get_id();
    std::cout<<"|num is "<<p1[0]<<'|'<<p1[1]<<'|'<<p1[2]<<endl;
}
int main(){
  vector<thread> threads;
  for(int i=0;i<10;i++){
      threads.push_back(thread(myPrint,i+1));
    }
  for(auto itr = threads.begin();itr!=threads.end();itr++){
      itr->join();//等待线程结束
    }
return 0;
}

2.数据有读有写得情况(加锁)
不加锁之前

class A{
public:
  void getNum(){
    for(int i=0;i<10000;i++){
        cout<<"存入的数据为:"<<i<<endl;
        data.push_back(i);
      }
  }
  void popNum(){
    for(int i=0;i<10000;i++){
        if(data.empty()){
            cout<<"数据不能为空"<<endl;
          }
        else {
            cout<<"首数字为:"<<data.front();
            data.pop_front();
          }
      }

  }
private:
  list<int> data;
};

int main(){
  A a;
  thread mythread1(&A::getNum,&a);
  thread mythread2(&A::popNum,&a);
  mythread1.join();
  mythread2.join();
return 0;
}

程序崩溃,因为数据在发生了同时的写和读的操作,导致数据错误。
所以引入一个概念,互斥量mutex:理解为一个锁(一个class)
只有一个线程能够锁成功,锁成功后会返回一个数据
lock() and unlock()要成对使用,有lock必须有unlock 必须一一对应。

class A{
public:
  void getNum(){
    for(int i=0;i<10000;i++){
        cout<<"存入的数据为:"<<i<<endl;
        my_mutex.lock();
        data.push_back(i);//保护数据段
        my_mutex.unlock();
      }
  }
  void popNum(){
    for(int i=0;i<10000;i++){
          my_mutex.lock();
        bool flag =data.empty();
        my_mutex.unlock();
        if(flag){
            cout<<"数据不能为空"<<endl;

          }
        else {
            my_mutex.lock();
            cout<<"首数字为:"<<data.front();
            data.pop_front();
            my_mutex.unlock();
          }

      }

  }
private:
  list<int> data;
  mutex my_mutex;
};

void test2(){
  A a;
  thread mythread1(&A::getNum,&a);
  thread mythread2(&A::popNum,&a);
  mythread1.join();
  mythread2.join();

}

加完mutex的lock和unlock后程序正常运行,不会出现错误。
为了防止忘记unlock,c++11提供了一个新的类模板lock_guard myguard(my_mutex);注意该方法的作用域的范范围是{ }在一个作用域中,把需要保护的数据放到一个圆括号中,例如

{
    lock_guard<mutex> myguard(my_mutex);
	data.push_back(i);//保护数据段
}//括号结束,lock_guard发生析构,进行解锁

在使用多个mutex的时候可能会出现死锁的情况。什么是死锁呢?,举个例子,假设我们有两把锁,首先我们用金锁去锁住数据,然后用银锁去锁住,在第一个线程先用金锁锁住数据的时候,突然线程二进入,线程二是先锁银锁再锁金锁,这个时候由于金锁是锁住的,线程二就在等待金锁的释放,而线程一呢,发现银锁是锁住的,也在等着银锁的释放,这样一来,两个人互等。发生死锁。我们可以现实生活中理解到,假设张三在北京等着李四从深圳来再出发,而李四在深圳等着张三从北京来再出发,两人就形成了死锁。所以说,在使用两个互斥量的的时候,一定要是相同的上锁顺顺序,这样一来就不会发生死锁现象
std::lock()同时锁住多个至少两个互斥量。不会发生死锁现象。
lock_guard与lock的成对使用

  void getNum(){
    for(int i=0;i<10000;i++){
        cout<<"存入的数据为:"<<i<<endl;
        lock(my_mutex,my_mutex2);
        data.push_back(i);
        my_mutex.unlock();
        my_mutex2.unlock();
      }
  }
void popNum(){
    for(int i=0;i<10000;i++){
        //lock_guard myguard(my_mutex);
        //my_mutex.lock();
        lock(my_mutex,my_mutex2);
        bool flag =data.empty();
        //my_mutex.unlock();
        if(flag){
            cout<<"数据不能为空"<<endl;

          }
        else {
            //my_mutex.lock();
            cout<<"首数字为:"<<data.front();
            data.pop_front();
            //my_mutex.unlock();
          }

         lock_guard<mutex> myguard(my_mutex,std::adopt_lock);
         lock_guard<mutex> myguard2(my_mutex2,std::adopt_lock);
      }

  }

3 unique_lock 取代lock_guard
1.std::adopt_lock 表示线程已经提前lock,不需要再构造函数中在此lock
2.std::try_to_lock尝试用lock去锁定mutex,哪怕没锁上也会立即返回。前提是不能用lock

class A{
public:
  void getNum(){
    for(int i=0;i<10000;i++){
        cout<<"存入的数据为:"<<i<<endl;
        unique_lock<mutex> mylock(my_mutex,try_to_lock);
        if(mylock.owns_lock()){
             data.push_back(i);
          }
        else {
            cout<<"拿不到锁。。。。。。"<<endl;
          }


      }
  }
  void popNum(){
    for(int i=0;i<10000;i++){
        //lock_guard myguard(my_mutex);
        //my_mutex.lock();
        lock(my_mutex,my_mutex2);
        bool flag =data.empty();
        Sleep(10);
        //my_mutex.unlock();
        if(flag){
            cout<<"数据不能为空"<<endl;

          }
        else {
            //my_mutex.lock();
            cout<<"首数字为:"<<data.front();
            data.pop_front();
            //my_mutex.unlock();
          }

         lock_guard<mutex> myguard(my_mutex,std::adopt_lock);
         lock_guard<mutex> myguard2(my_mutex2,std::adopt_lock);
      }

  }
private:
  list<int> data;
  mutex my_mutex;
  mutex my_mutex2;
};

void test2(){
  A a;
  thread mythread1(&A::getNum,&a);
  thread mythread2(&A::popNum,&a);
  mythread1.join();
  mythread2.join();

}

3.defer_lock 并没有加锁,初始化一个mutex类。
uniquck_lock 有自带的lock和unlock方法,可以随时解锁,上锁。解锁后处理非共享数据,枷锁后处理共享数据

小结

本章节主要介绍了一些多线程的基本用法,能够满足基本的使用。能够满足简单的实现多线程代码。
下篇将继续深入探讨多线程的内容。

你可能感兴趣的:(C++语言学习)