zoukankan      html  css  js  c++  java
  • C++多线程__新__研二下

    目录

    一、并发、进程、线程的基本概念

    二、并发的实现方法

    1、多进程并发

    2、多线程并发

    三、join和detach方法

    1、join方法

    2、detach方法

    3、joinable方法

    4、类对象也可以成为可调用对象

    5、类中的私有数据含有引用或者是指针时候会出现意外

    6、用Lamda表达式创建子线程(此处只写出了主函数)

    四、共享数据的保护问题

    1、创建多个线程

    2、 数据共享问题分析

      4.2.1、只读数据:不去给数据重新赋值,此时是没有问题的

      4.2.2、 有读有写:读的时候不能写,写的时候不能读,可能存在线程1还没有对变量写完,线程2又对该变量去操作了

      4.2.3 使用std::lock_guard()替代lock()和unlock()替代lock()和unock()

      4.2.4、死锁(至少有两个互斥量,即两把锁)

      4.2.5、死锁的解决方法:五种方法

    五、unique_lock()的讲解

    1、unique_lock()取代lock_guard(),unique_lock和lock_guard一样,在它们类的构造函数中加锁,在析构函数中解锁

    2、std::adopt_lock 表示前面前面已经为mutex对象加锁,在unique_lock的构造函数中不需要再次加锁

    3、std::try_to_lock  尝试加锁,如果加锁失败,可以做其他的事情,而不是阻塞住

    4、std::defer_lock 初始化一个没有加锁的mutex对象,只是使unique_lock起到绑定mutex对象的作用,接下来可以使用unique_lock类对象的成员函数lock()对mutex对象加锁

    5、unique_lock()的成员函数

      (1)lock()和unlock()

      (2)try_lock()

      (3)release()

    6、unique_lock()所有权的传递

    六、单例设计模式共享数据分析、解决、call_once

    1、设计模式概述 

    2、单例设计模式---单例类

    3、单例模式下解决不同线程访问共享数据或方法的问题

    4、call_once()函数

    七、条件变量 std::condition_varianle、wait()、notify_one()、notify_all()

    1、提高效率的方法

    (1)双重判断以提高效率

    (2)std::condition_variable类和该类中的wait()、notify_one()方法

    (3)std::condition_variable类和该类中的wait()、notify_all()方法

    一、并发、进程、线程的基本概念

    1、并发:两个或者更多的任务(独立的活动)同时进行,一个程序同时执行多个独立的任务。以往计算机只是单核CPU,某一个时刻只能执行一个任务,由操作系统调度,每秒钟多次进行任务切换,不是真正的并发。现在计算机多是多核CPU,能够真正的并行执行多个任务(硬件并发)
    2、可执行程序:磁盘上的文件,windows下扩展名为.exe的文件
    3、进程:一个可执行程序运行起来就叫一个进程运行起来了,或者说进程就是运行起来了的可执行程序
    4、线程:每个进程都有一个主线程(自动创建,一般是main函数),实际上运行程序的时候,实际上是该进程的主线程在运行,线程就是执行代码的一条道路,除了主线程之外,可以自己写代码创建其他线程,每创建一个新线程就可以多干一个不同的事,但是线程并不是越多越好(子线程最多不可超过300个),每个线程都需要一个独立的堆栈空间,线程之间的切换是要保存中间数据的,会耗费本该是程序运行的时间
    总结线程:
    a 线程是用来执行代码的
    b 把线程理解为一个新的通路
    c 一个进程自动包含一个主线程,主线程随着进程的自动启动和结束
    d 多线程程序可以同时做多个事

    二、并发的实现方法

    a)多个进程实现并发
    b)在单独的一个进程中,创建多个线程实现并发
    1、多进程并发
    world ie浏览器 爱奇艺等软件同时运行
    进程之间的通信(同一台电脑)方法:管道、文件、消息队列等
    进程之间的通信(不同电脑)方法:socket通信技术
    2、多线程并发
    每个线程都有自己独立的运行路径,且共享地址空间(共享内存)
    全局变量、指针、引用都可以在不同线程之间传递,共享内存也胡存在一些问题,如数据一致性问题,例如线程1和线程2同时对一个变量进行操作时候,就会出现意外。
    总结:线程如下优点
    1)线程启动速度比进程快
    2)系统资源开销更少
    缺点:存在数据一致性问题
    三、C++11新标准线程库(可以跨平台widows和Linux)
    C++11增加了对多线程的支持,意味着可移植性

    三、join和detach方法

    1、join方法

    程序运行起来,生成一个进程,该进程所在的主进程自动运行
    自己创建的线程也需要从一个函数开始执行,如果这个函数执行完毕该线程也运行结束
    一般情况下,如果主线程执行完毕,子线程还没有结束,那么整个子线程会被系统强行终止
    所以一般情况下,如果想保持子线程的运行状态,就要让主线程一直保持运行

    1、thread是标准库中的一个类
    2、join阻塞主线程,让主线程等待子线程执行完毕,
    实例:

     1 #include <iostream>
     2 #include <thread> //尖括号表示系统头文件
     3 
     4 using namespace std;
     5 
     6 void myprint()
     7 {
     8 cout<<"我的线程开始执行"<<endl;
     9 //可以做一些其他的事情
    10 cout<<"我的线程执行结束"<<endl;
    11 }
    12 
    13 int main()
    14 {
    15 thread mytobj(myprint); //创建了线程,该线程执行起点为myprint(),该线程开始执行
    16 
    17 //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的
    18 
    19 mytobj.join(); //主线程阻塞到这里,子线程继续执行,主线程等待子线程执行完毕,当子线程执行完毕,主线程就继续向下执行
    20 
    21 
    22 cout<<"HelloWorld"<<endl;
    23 
    24 return 0;
    25 }
    View Code

    //如果不加join()此时这个代码有两条线同时在跑,打印"我的线程开始执行"和打印"HelloWorld"是通过不同线路来打印的

    2、detach方法

    传统主线程要等待子线程执行完毕再推出,但是主线程也可以和子线程分离,即主线程可以提前结束,以提高程序运行效率
    一旦detach()之后,与这个主线程关联的线程对象就会失去了与主线程join的资格
    此时子线程在后台运行,这个子线程被C++运行时库接管

     1 #include <iostream>
     2 #include <thread> //尖括号表示系统头文件
     3 
     4 using namespace std;
     5 
     6 void myprint()
     7 {
     8 cout<<"我的线程开始执行"<<endl;
     9 //可以做一些其他的事情
    10 cout<<"我的线程执行结束1"<<endl;
    11 cout<<"我的线程执行结束2"<<endl;
    12 cout<<"我的线程执行结束3"<<endl;
    13 cout<<"我的线程执行结束4"<<endl;
    14 cout<<"我的线程执行结束5"<<endl;
    15 cout<<"我的线程执行结束6"<<endl;
    16 cout<<"我的线程执行结束7"<<endl;
    17 cout<<"我的线程执行结束8"<<endl;
    18 cout<<"我的线程执行结束9"<<endl;
    19 cout<<"我的线程执行结束10"<<endl;
    20 cout<<"我的线程执行结束11"<<endl;
    21 }
    22 
    23 int main()
    24 {
    25 thread mytobj(myprint); //创建了线程,该线程执行起点为myprint(),该线程开始执行
    26 
    27 //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的
    28 
    29 mytobj.detach(); //主线程阻塞到这里,子线程继续执行,主线程等待子线程执行完毕,当子线程执行完毕,主线程就继续向下执行
    30 
    31 
    32 cout<<"HelloWorld1"<<endl;
    33 cout<<"HelloWorld2"<<endl;
    34 cout<<"HelloWorld3"<<endl;
    35 cout<<"HelloWorld4"<<endl;
    36 cout<<"HelloWorld5"<<endl;
    37 cout<<"HelloWorld6"<<endl;
    38 cout<<"HelloWorld7"<<endl;
    39 
    40 return 0;
    41 }
    View Code

    打印可能是:

     1 我的线程开始执行
     2 我的线程执行结束1
     3 我的线程执行结束2
     4 HelloWorld1
     5 HelloWorld2
     6 HelloWorld3
     7 HelloWorld4
     8 我的线程执行结束8
     9 我的线程执行结束9
    10 我的线程执行结束3
    11 HelloWorld5
    12 HelloWorld6
    13 HelloWorld7
    14 执行完毕
    View Code

    //此时子线程和主线程同时执行,但是这样可能会存在主线内的代码执行完毕,但是子线程中的代码还没有执行完毕的现象

    3、detachable():判断是否可以使用join或detach,返回true表示可以join,否则表示不可join

    4、类对象也为可调用对象

     1 例如:
     2 #include <iostream>
     3 #include <thread> //尖括号表示系统头文件
     4 
     5 using namespace std;
     6 
     7 class TA
     8 {
     9 public:
    10 void operator()(); //不能带参数
    11 {
    12 cout<<"子线程开始1"<<endl;
    13 cout<<"子线程开始2"<<endl;
    14 cout<<"子线程开始3"<<endl;
    15 cout<<"子线程开始4"<<endl;    
    16 }
    17 
    18 };
    19 
    20 void myprint()
    21 {
    22 cout<<"我的线程开始执行"<<endl;
    23 //可以做一些其他的事情
    24 cout<<"我的线程执行结束1"<<endl;
    25 cout<<"我的线程执行结束2"<<endl;
    26 cout<<"我的线程执行结束3"<<endl;
    27 cout<<"我的线程执行结束4"<<endl;
    28 cout<<"我的线程执行结束5"<<endl;
    29 cout<<"我的线程执行结束6"<<endl;
    30 cout<<"我的线程执行结束7"<<endl;
    31 cout<<"我的线程执行结束8"<<endl;
    32 cout<<"我的线程执行结束9"<<endl;
    33 cout<<"我的线程执行结束10"<<endl;
    34 cout<<"我的线程执行结束11"<<endl;
    35 }
    36 
    37 int main()
    38 {
    39 TA ta;
    40 thread mytobj3(ta); //ta为可调用对象 类对象也为可调用对象
    41 mytobj3.join(); //等待子线程执行结束,也可以用detach()
    42 
    43 //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的
    44 
    45 cout<<"HelloWorld1"<<endl;
    46 
    47 return 0;
    48 }
    View Code

    5、类中的私有数据含有引用或者是指针时候会出现意外

    如下代码:

     1 #include <iostream>
     2 #include <thread>    //尖括号表示系统头文件
     3 
     4 using namespace std;
     5 
     6 class TA
     7 {
     8     int & m_i;
     9     public:
    10     TA(int & i):m_i(i);
    11     void operator()()  //不能带参数
    12     {
    13         cout<<"m_i的值为"<<m_i<<endl;
    14         cout<<"m_i的值为"<<m_i<<endl;
    15         cout<<"m_i的值为"<<m_i<<endl;
    16         cout<<"m_i的值为"<<m_i<<endl;
    17         cout<<"m_i的值为"<<m_i<<endl;
    18         cout<<"m_i的值为"<<m_i<<endl;
    19         cout<<"m_i的值为"<<m_i<<endl;
    20         
    21     }
    22     
    23 };
    24 
    25 void myprint()
    26 {
    27     cout<<"我的线程开始执行"<<endl;
    28     //可以做一些其他的事情
    29     cout<<"我的线程执行结束1"<<endl;
    30     cout<<"我的线程执行结束2"<<endl;
    31     cout<<"我的线程执行结束3"<<endl;
    32     cout<<"我的线程执行结束4"<<endl;
    33     cout<<"我的线程执行结束5"<<endl;
    34     cout<<"我的线程执行结束6"<<endl;
    35     cout<<"我的线程执行结束7"<<endl;
    36     cout<<"我的线程执行结束8"<<endl;
    37     cout<<"我的线程执行结束9"<<endl;
    38     cout<<"我的线程执行结束10"<<endl;
    39     cout<<"我的线程执行结束11"<<endl;
    40 }
    41 
    42 int main()
    43 { 
    44     int myi=6;
    45     TA ta(myi);
    46     thread mytobj3(ta);  
    47     mytobj3.detach();     
    48     
    49     //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的
    50     
    51     cout<<"HelloWorld1"<<endl;
    52     
    53     return 0;
    54 }
    View Code

    主线程先执行完,子线程还在执行,myi是主线程的变量,主线程执行完myi就会被销毁,子线程再去使用myi的时候就会出错
    或者将类TA中的公有数据改成不是引用也是可以的,这样就会使用主线程中myi的拷贝,这样就没有问题了

    主线程结束后,ta也会被销毁,但是ta不在没有关系,ta是会被复制到了子线程中去的(所以在类中要有复制构造函数),所以不会因此出错,如下例程:
    只要类中私有数据没有引用、指针,使用detach()就没有问题

     1 #include <iostream>
     2 #include <thread>    //尖括号表示系统头文件
     3 
     4 using namespace std;
     5 
     6 class TA
     7 {
     8 private:
     9     int  m_i;
    10 public:
    11     TA(int & i) :m_i(i)
    12     {
    13         cout << "构造函数被执行" << endl;
    14     }
    15     TA(const TA & ta) :m_i(ta.m_i)
    16     {
    17         cout << "复制构造函数被执行" << endl;
    18     }
    19     ~TA()
    20     {
    21         cout << "析构函数被执行" << endl;
    22     }
    23 
    24     void operator()()  //不能带参数
    25     {
    26         cout << "m_i1的值为" << m_i << endl;
    27         cout << "m_i2的值为" << m_i << endl;
    28         cout << "m_i3的值为" << m_i << endl;
    29         cout << "m_i4的值为" << m_i << endl;
    30         cout << "m_i5的值为" << m_i << endl;
    31         cout << "m_i6的值为" << m_i << endl;
    32         cout << "m_i7的值为" << m_i << endl;
    33     }
    34 
    35 };
    36 
    37 int main()
    38 {
    39     int myi = 6;
    40     TA ta(myi);
    41     thread mytobj3(ta);
    42     mytobj3.join();
    43 
    44     //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的
    45 
    46     cout << "HelloWorld1" << endl;
    47 
    48     system("pause");
    49     return 0;
    50 }
    View Code

    执行结果:

     但是主线程中的类对象ta执行析构函数没有显示不知道为啥。。注:以上方法也可以改为detach()

    6、用Lamda表达式创建子线程(此处只写出了主函数)

     1 int main()
     2 {
     3     auto mylambdathread = [] 
     4     {
     5         cout<<"我的子线程开始执行"<<endl;
     6     }
     7     thread mytobj(mylambdathread);
     8     mytobj.join();
     9     
    10     return 0;
    11 }
    View Code

    四、 共享数据的保护问题

    1、创建多个线程

    01)多个线程的执行顺序是乱的,有可能线程1还没有结束,就去执行线程2了,这个和系统的运行调度机制有关
    02)子线程等待所有子线程结束,主线程结束(使用join方法)
    03)把thread对象放到容器里面,这对创建大量线程并对大量线程管理很方便

     1 //创建多个线程
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <vector>
     5 
     6 using namespace std;
     7 
     8 //线程入口函数,但是可以给多个线程使用
     9 void myprint(int value)
    10 {
    11     cout << "子线程开始执行,编号=" << value << endl;
    12 
    13     //可以做一些其他的事情
    14 
    15     cout << "子线程执行结束,编号=" << value << endl;
    16 }
    17 
    18 int main()
    19 {
    20     vector <thread> mythreads;
    21 
    22     for (int i = 0; i < 10; i++)
    23     {
    24         mythreads.push_back(thread(myprint, i));  //创建十个线程,并开始执行,其中i作为实参传入myprint()中
    25     }
    26     for (auto iter = mythreads.begin(); iter != mythreads.end(); iter++)
    27     {
    28         iter->join();  //等待十个线程执行完毕
    29     }
    30 
    31     cout << "HelloWorld" << endl;
    32 
    33     system("pause");
    34     return 0;
    35 }
    实例

    执行结果1:

        

    2、 数据共享问题分析

    4.2.1、只读数据:不去给数据重新赋值,此时是没有问题的

     1 #include <iostream>
     2 #include <thread>    //尖括号表示系统头文件
     3 
     4 using namespace std;
     5 
     6 vector<int> g_v = {1,2,3};  //多线程共享数据
     7 
     8 //myprint()只读了g_v的数据,每个线程并没有去给g_V写入数据
     9 void myprint(int value)
    10 {
    11     cout<<"id为:"<<std::this_thread::get_id()<<"的g_v的值为"<<g_v[1]<<","<<g_v[2]<<endl;
    12 }
    13 
    14 int main()
    15 {
    16     vector <thread> mythreads;
    17     
    18     for(int i=0;i<10;i++)
    19     {
    20         mythreads.push_back(myprint,i);  //创建十个线程,并开始执行
    21     }
    22     for(auto iter=mythreads.begin();iter!=mythreads.end();iter++)
    23     {
    24         iter->join();  //等待十个线程执行完毕
    25     }    
    26     
    27     cout<<"HelloWorld"<<endl;
    28     
    29     system("pause");
    30     return 0;
    31 }
    多个线程只是读数据

    4.2.2、 有读有写:读的时候不能写,写的时候不能读,可能存在线程1还没有对变量写完,线程2又对该变量去操作了

    数据案例保护案例:
    开发一个网络服务器,有啷个线程,线程1:收集玩家命令,线程2:从队列中取出命令并解析
    假定玩家发的命令为一个数字,list也是一个容器,list在频繁的删除和插入数据效率较高,vector随机插入和删除效率较高
    (1)互斥量:来解决多线程共享数据的保护问题
    锁:某个线程用代码把共享数据锁住,其他线程如果想操作共享数据,必须等待解锁
    互斥量的基本概念:一个类对象,理解成一把锁,多个线程使用lock(),只有一个线程锁住成功,如果没有锁成功,那么就会卡在lock()这里,
    只保护需要保护的数据,少l起不到保护效果,多了影响效率
    (2)lock()和unlock()的使用
    步骤:先lock()、操作共享数据(读写)、unlock()
    lock()和unlock()要成对使用

     1 #include <iostream>
     2 #include <thread>    //尖括号表示系统头文件
     3 #include <list>
     4 #include <mutex>     //lock()
     5 
     6 using namespace std;
     7 
     8 class A
     9 {
    10 public:
    11     //把收到的信息(玩家命令)放入到一个队列的线程
    12     void inMsgRecvQueue()
    13     {
    14         for (int i = 0; i < 100000; i++)
    15         {
    16             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    17             my_mutex.lock();            //如果线程2中的outMsgLULProc()被锁住,那么这里就不会被锁住,继续执行lock()下面的代码
    18             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    19             my_mutex.unlock();          //只有在对msgRecvQueue写数据的这一句需要保护,其余不需要保护,如果把for循环全保护,那么效率就会降低
    20         }
    21     }
    22     bool outMsgLULProc(int &command)
    23     {
    24         //如果线程1中的inMsgRecvQueue()被锁住,那么这里就不会被锁住,继续执行lock()下面的代码
    25         my_mutex.lock();               //使用同一把锁对线程2也加锁
    26         if (!msgRecvQueue.empty())
    27         {
    28             //消息不为空
    29             command = msgRecvQueue.front();
    30             msgRecvQueue.pop_front();  //移除取出的元素
    31             //以下可以考虑处理数据...
    32             my_mutex.unlock();          //每一个分支都得由unlock()
    33             return true;
    34         }
    35         else
    36         {
    37             my_mutex.unlock();        //每一个分支都得由unlock()
    38             return false;
    39         }
    40     }
    41 
    42     //把数据从消息队列中取出的线程
    43     void outMsgRecvQueue()
    44     {
    45         int command = 0;
    46 
    47         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    48         {
    49             bool result = outMsgLULProc(command);
    50             if (result == true)
    51             {
    52                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    53                 //以下可以考虑处理数据
    54             }
    55         }
    56     }
    57 
    58 private:
    59     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    60     std::mutex my_mutex;  //创建一个互斥量
    61 };
    62 int main()
    63 {
    64     A myobja;  //生成类对象
    65     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    66     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    67     myOutMsgObj.join();
    68     myInMsgObj.join();
    69 
    70     system("pause");
    71     return 0;
    72 }
    使用lock()和unlock()保护共享数据

    线程1中的inMsgRecvQueue()会操作共享数据,线程2中的outMsgRecvQueue()也会操作共享数据,此时就需要对两个线程中操作共享数据的那句代码分别加锁和解锁
    但是那个线程先加锁成功是由系统决定的,假如线程1中的inMsgRecvQueue()先加所锁成功(会继续执行lock()以下的代码),那么线程2中的outMsgRecvQueue()就不
    会加锁成功(即卡在lock(),程序不会向下执行outMsgRecvQueue()中lock()以下的代码)

    4.2.3 使用std::lock_guard()替代lock()和unlock()替代lock()和unock()

    C++使用std::lock_guard()可以同时取代lock()和unlock()
    原理是lock_guard()是一个类模板,在该类中的构造函数中使用了lock(),在该类的析构函数中使用了unlock()
    由于一般是在一个调用函数中使用lock_guard(),即会创建局部类对象,那么在该调用函数结束时就会调用lock_guard()类对象的析构函数
    但是lock_guard()也有缺点,就是想当于在调用函数的最后使用unlock(),解决方法是使用大括号让lock_guard()提前结束生命周期

     1 #include <iostream>
     2 #include <thread>    //尖括号表示系统头文件
     3 #include <list>
     4 #include <mutex>     //lock()
     5 
     6 using namespace std;
     7 
     8 class A
     9 {
    10 public:
    11     //把收到的信息(玩家命令)放入到一个队列的线程
    12     void inMsgRecvQueue()
    13     {
    14         for (int i = 0; i < 100000; i++)
    15         {
    16             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    17             std::lock_guard<std::mutex> sbguard(my_mutex);  //sbguard为lock_guard的类对象,my_mutex为在private中定义的互斥量
    18             //my_mutex.lock();
    19             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    20             //my_mutex.unlock();          //只有在对msgRecvQueue写数据的这一句需要保护,其余不需要保护,如果把for循环全保护,那么效率就会降低
    21         }
    22     }
    23     bool outMsgLULProc(int &command)
    24     {
    25         std::lock_guard<std::mutex> sbguard(my_mutex);  //sbguard为lock_guard的类对象,my_mutex为在private中定义的互斥量
    26         if (!msgRecvQueue.empty())
    27         {
    28             //消息不为空
    29             command = msgRecvQueue.front();
    30             msgRecvQueue.pop_front();  //移除取出的元素
    31             //以下可以考虑处理数据...
    32             //my_mutex.unlock();          //使用lock_guard就不必再使用lock()和unlock()
    33             return true;
    34         }
    35         else
    36         {
    37             //my_mutex.unlock();        //使用lock_guard就不必再使用lock()和unlock()
    38             return false;
    39         }
    40     }
    41 
    42     //把数据从消息队列中取出的线程
    43     void outMsgRecvQueue()
    44     {
    45         int command = 0;
    46 
    47         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    48         {
    49             bool result = outMsgLULProc(command);
    50             if (result == true)
    51             {
    52                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    53                 //以下可以考虑处理数据
    54             }
    55         }
    56     }
    57 
    58 private:
    59     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    60     std::mutex my_mutex;  //创建一个互斥量
    61 };
    62 int main()
    63 {
    64     A myobja;  //生成类对象
    65     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    66     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    67     myOutMsgObj.join();
    68     myInMsgObj.join();
    69 
    70     system("pause");
    71     return 0;
    72 }
    使用lock_guard()保护共享数据

    4.2.4、死锁(至少有两个互斥量,即两把锁)

    一个互斥量是一把锁
    线程A、线程B 锁1、锁2
    线程A把锁1lock()成功,那么就会继续执行下面的代码,然后线程A又去lock()锁2,但是还没有lock()成功,突然因为线程调度,线程B开始执行,
    因为线程A已经把锁1lock成功了,所以线程B肯定不会将锁1lock成功,线程B先lock()锁2,且lock成功(因为线程A还没有把锁2lock成功),之后线程2
    要去lock锁1,此时死锁就发生了。
    此时的情况是:
    线程Alock着锁1不松手(对锁1没有unlock()),且卡在了锁2那里,后面的代码不可执行下去;
    线程Block着锁2不松手(对锁2没有unlock()),且卡在了锁1那里,后面的代码不可执行下去。

    线程1先lock锁1再lock锁2
    线程2先lock锁2再lock锁1
    就会出现死锁的现象

     1 //线程1先lock锁1再lock锁2
     2 //线程2先lock锁2再lock锁1
     3 //就会出现死锁的现象
     4 #include <iostream>
     5 #include <thread>    //尖括号表示系统头文件
     6 #include <list>
     7 #include <mutex>     //lock()
     8 
     9 using namespace std;
    10 
    11 class A
    12 {
    13 public:
    14     //把收到的信息(玩家命令)放入到一个队列的线程
    15     void inMsgRecvQueue()
    16     {
    17         for (int i = 0; i < 100000; i++)
    18         {
    19             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    20             //可以加上大括号让lock_guard()提前结束生命周期  
    21             my_mutex1.lock();
    22             //中间可能隔了很多代码(需要保护不同的数据块)
    23             my_mutex2.lock();
    24             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    25             my_mutex1.unlock();          //只有在对msgRecvQueue写数据的这一句需要保护,其余不需要保护,如果把for循环全保护,那么效率就会降低
    26             my_mutex2.unlock();
    27         }
    28     }
    29     bool outMsgLULProc(int &command)
    30     {
    31         if (!msgRecvQueue.empty())
    32         {
    33             //消息不为空
    34             my_mutex2.lock();
    35             my_mutex1.lock();
    36             command = msgRecvQueue.front();
    37             msgRecvQueue.pop_front();  //移除取出的元素
    38             //以下可以考虑处理数据...
    39             my_mutex1.unlock();          //使用lock_guard就不必再使用lock()和unlock()
    40             my_mutex2.unlock();
    41             return true;
    42         }
    43         else
    44         {
    45             //先解锁哪个都是可以的
    46             my_mutex1.unlock();        //使用lock_guard就不必再使用lock()和unlock()
    47             my_mutex2.unlock();
    48             return false;
    49         }
    50     }
    51 
    52     //把数据从消息队列中取出的线程
    53     void outMsgRecvQueue()
    54     {
    55         int command = 0;
    56 
    57         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    58         {
    59             bool result = outMsgLULProc(command);
    60             if (result == true)
    61             {
    62                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    63                 //以下可以考虑处理数据
    64             }
    65         }
    66     }
    67 
    68 private:
    69     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    70     std::mutex my_mutex1;  //创建一个互斥量1
    71     std::mutex my_mutex2;  //创建一个互斥量2
    72 };
    73 int main()
    74 {
    75     A myobja;  //生成类对象
    76     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    77     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    78     myOutMsgObj.join();
    79     myInMsgObj.join();
    80 
    81     system("pause");
    82     return 0;
    83 }
    死锁实例

    4.2.5、死锁的解决方法

    方法一:使用lock()和unlock(),且保证不能线程之间对互斥量lock()的顺序一致
    只要保证两个互斥量(锁)lock()的顺序一致就不会出现死锁的现象
    使用lock_guard也是要保证两个线程lock_guard()两个互斥量的顺序是 一样的即可
    比如线程1中:
    std::lock_guard<std::mutex> sbguard(my_mutex1);
    std::lock_guard<std::mutex> sbguard(my_mutex2);
    那么线程2中的顺序也得是:
    std::lock_guard<std::mutex> sbguard(my_mutex1);
    std::lock_guard<std::mutex> sbguard(my_mutex2);

     1 //上面出现死锁的解决方法:把线程1和线程2lock()两个互斥量的顺序改成一致即可
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <list>
     5 #include <mutex>     //lock()
     6 
     7 using namespace std;
     8 
     9 class A
    10 {
    11 public:
    12     //把收到的信息(玩家命令)放入到一个队列的线程
    13     void inMsgRecvQueue()
    14     {
    15         //for (int i = 0; i < 100000; i++)
    16         //{
    17             cout << "inMsgRecvQueue执行,插入一个数据:" << 1 << endl; 
    18             my_mutex1.lock();
    19             cout << "inMsgRecvQueue()中的my_mutex1已经被锁住" << endl;
    20             //中间可能隔了很多代码(需要保护不同的数据块)
    21             my_mutex2.lock();
    22             cout << "inMsgRecvQueue()中的my_mutex2已经被锁住" << endl;
    23             msgRecvQueue.push_back(1);  //假设数字i就是收到的玩家命令
    24             my_mutex1.unlock();          //只有在对msgRecvQueue写数据的这一句需要保护,其余不需要保护,如果把for循环全保护,那么效率就会降低
    25             cout << "inMsgRecvQueue()中的my_mutex1已经被解锁" << endl;
    26             my_mutex2.unlock();
    27             cout << "inMsgRecvQueue()中的my_mutex2已经被解锁" << endl;
    28         //}
    29     }
    30     bool outMsgLULProc(int &command)
    31     {
    32         if (!msgRecvQueue.empty())
    33         {
    34             //消息不为空
    35             my_mutex1.lock();  //这里必须和inMsgRecvQueue()中锁my_mutex1和my_mutex2的顺序是一样的
    36             //cout << "outMsgLULProc(int &command)中的my_mutex1已经被锁住" << endl;
    37             my_mutex2.lock();
    38             cout << "outMsgLULProc(int &command)中的my_mutex2已经被锁住" << endl;
    39             command = msgRecvQueue.front();
    40             msgRecvQueue.pop_front();  //移除取出的元素
    41             //以下可以考虑处理数据...
    42             my_mutex1.unlock();          //使用lock_guard就不必再使用lock()和unlock()
    43             cout << "outMsgLULProc(int &command)中的my_mutex1已经被解锁" << endl;
    44             my_mutex2.unlock();
    45             cout << "outMsgLULProc(int &command)中的my_mutex1已经被解锁" << endl;
    46             return true;
    47         }
    48         else
    49         {
    50             //先解锁哪个都是可以的
    51             my_mutex1.unlock();        //使用lock_guard就不必再使用lock()和unlock()
    52             my_mutex2.unlock();
    53             return false;
    54         }
    55     }
    56 
    57     //把数据从消息队列中取出的线程
    58     void outMsgRecvQueue()
    59     {
    60         int command = 0;
    61 
    62         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    63         {
    64             bool result = outMsgLULProc(command);
    65             if (result == true)
    66             {
    67                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    68                 //以下可以考虑处理数据
    69             }
    70         }
    71     }
    72 
    73 private:
    74     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    75     std::mutex my_mutex1;  //创建一个互斥量1
    76     std::mutex my_mutex2;  //创建一个互斥量2
    77 };
    78 int main()
    79 {
    80     A myobja;  //生成类对象
    81     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    82     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    83     myOutMsgObj.join();
    84     myInMsgObj.join();
    85 
    86     system("pause");
    87     return 0;
    88 }
    使用lock和unlock解决死锁(保证不同线程之间对互斥量lock()的顺序一致)

    这样还是有问题的,出现unlock of unknowed mutex,刚刚百度了一下,可能的原因是:
    (函数1)unlock,(函数2)lock,(函数1)delete,(函数2)unlock。

    方法二:使用lock_guard(),且保证不能线程之间对互斥量lock()的顺序一致

     1 //使用lock_guard()可以解决上述死锁的问题,也就是要保证线程1和线程2中的两个互斥量lock_guard()的顺一致
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <list>
     5 #include <mutex>     //lock()
     6 
     7 using namespace std;
     8 
     9 class A
    10 {
    11 public:
    12     //把收到的信息(玩家命令)放入到一个队列的线程
    13     void inMsgRecvQueue()
    14     {
    15         for (int i = 0; i < 100000; i++)
    16         {
    17             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    18             std::lock_guard<std::mutex> sbguard1(my_mutex1);
    19             std::lock_guard<std::mutex> sbguard2(my_mutex2);
    20             //my_mutex1.lock();
    21             //中间可能隔了很多代码(需要保护不同的数据块)
    22             //my_mutex2.lock();
    23             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    24             //my_mutex1.unlock();          //只有在对msgRecvQueue写数据的这一句需要保护,其余不需要保护,如果把for循环全保护,那么效率就会降低
    25             //my_mutex2.unlock();
    26         }
    27     }
    28     bool outMsgLULProc(int &command)
    29     {
    30         if (!msgRecvQueue.empty())
    31         {
    32             //消息不为空
    33             //my_mutex1.lock();  //这里必须和inMsgRecvQueue()中锁my_mutex1和my_mutex2的顺序是一样的
    34             //my_mutex2.lock();
    35             std::lock_guard<std::mutex> sbguard1(my_mutex1);
    36             std::lock_guard<std::mutex> sbguard2(my_mutex2);
    37             command = msgRecvQueue.front();
    38             msgRecvQueue.pop_front();  //移除取出的元素
    39             //以下可以考虑处理数据...
    40             //my_mutex1.unlock();          //使用lock_guard就不必再使用lock()和unlock()
    41             //my_mutex2.unlock();
    42             return true;
    43         }
    44         else
    45         {
    46             //先解锁哪个都是可以的
    47             //my_mutex1.unlock();        //使用lock_guard就不必再使用lock()和unlock()
    48             //my_mutex2.unlock();
    49             return false;
    50         }
    51     }
    52 
    53     //把数据从消息队列中取出的线程
    54     void outMsgRecvQueue()
    55     {
    56         int command = 0;
    57 
    58         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    59         {
    60             bool result = outMsgLULProc(command);
    61             if (result == true)
    62             {
    63                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    64                 //以下可以考虑处理数据
    65             }
    66         }
    67     }
    68 
    69 private:
    70     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    71     std::mutex my_mutex1;  //创建一个互斥量1
    72     std::mutex my_mutex2;  //创建一个互斥量2
    73 };
    74 int main()
    75 {
    76     A myobja;  //生成类对象
    77     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    78     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    79     myOutMsgObj.join();
    80     myInMsgObj.join();
    81 
    82     system("pause");
    83     return 0;
    84 }
    使用lock_guard()可以解决上述死锁的问题

    方法三:使用std::lock()类模板

    能力:同时锁住两个或者是连个以上的互斥量,不存在出现死锁的问题
    std::lock():如果锁1没有锁住,那么就等待在那里,同时解锁已经锁住了的互斥量,等到锁1锁住了的时候,再去锁住已经解锁了的互斥量

    使用方法:
    std::lock(mutex1,mutex2,mutex3); //相当于每个互斥量都调用了lock()
    解锁时同时对mutex1,mutex2,mutex3同时解锁
    my_mutex1.unlock()
    my_mutex2.unlock()
    my_mutex3.unlock()

    使用std::lock()类模板互斥量的顺序可以不一样,如
    在线程1中:
    std::lock(mutex1,mutex2);
    my_mutex1.unlock()
    my_mutex2.unlock()

    在线程2中可以是:
    std::lock(mutex2,mutex1);
    my_mutex1.unlock()
    my_mutex2.unlock()

     1 //使用lock()类模板解决上述死锁的问题
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <list>
     5 #include <mutex>     //lock()
     6 
     7 using namespace std;
     8 
     9 class A
    10 {
    11 public:
    12     //把收到的信息(玩家命令)放入到一个队列的线程
    13     void inMsgRecvQueue()
    14     {
    15         for (int i = 0; i < 100000; i++)
    16         {
    17             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    18             std::lock(my_mutex1, my_mutex2);
    19             //std::lock_guard<std::mutex> sbguard1(my_mutex1);
    20             //std::lock_guard<std::mutex> sbguard2(my_mutex2);
    21             //my_mutex1.lock();
    22             //中间可能隔了很多代码(需要保护不同的数据块)
    23             //my_mutex2.lock();
    24             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    25             my_mutex1.unlock();          //只有在对msgRecvQueue写数据的这一句需要保护,其余不需要保护,如果把for循环全保护,那么效率就会降低
    26             my_mutex2.unlock();
    27         }
    28     }
    29     bool outMsgLULProc(int &command)
    30     {
    31         if (!msgRecvQueue.empty())
    32         {
    33             //消息不为空
    34             //my_mutex1.lock();  //这里必须和inMsgRecvQueue()中锁my_mutex1和my_mutex2的顺序是一样的
    35             //my_mutex2.lock();
    36             std::lock(my_mutex1, my_mutex2);
    37             //std::lock_guard<std::mutex> sbguard1(my_mutex1);
    38             //std::lock_guard<std::mutex> sbguard2(my_mutex2);
    39             command = msgRecvQueue.front();
    40             msgRecvQueue.pop_front();  //移除取出的元素
    41             //以下可以考虑处理数据...
    42             my_mutex1.unlock();          //使用lock_guard就不必再使用lock()和unlock()
    43             my_mutex2.unlock();
    44             return true;
    45         }
    46         else
    47         {
    48             //先解锁哪个都是可以的
    49             my_mutex1.unlock();        //使用lock_guard就不必再使用lock()和unlock()
    50             my_mutex2.unlock();
    51             return false;
    52         }
    53     }
    54 
    55     //把数据从消息队列中取出的线程
    56     void outMsgRecvQueue()
    57     {
    58         int command = 0;
    59 
    60         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    61         {
    62             bool result = outMsgLULProc(command);
    63             if (result == true)
    64             {
    65                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    66                 //以下可以考虑处理数据
    67             }
    68         }
    69     }
    70 
    71 private:
    72     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    73     std::mutex my_mutex1;  //创建一个互斥量1
    74     std::mutex my_mutex2;  //创建一个互斥量2
    75 };
    76 int main()
    77 {
    78     A myobja;  //生成类对象
    79     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    80     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    81     myOutMsgObj.join();
    82     myInMsgObj.join();
    83 
    84     system("pause");
    85     return 0;
    86 }
    使用lock()类模板解决上述死锁的问题

    这个也不是很好用。。。

    方法五:将std::lock()类模板和lock_guard()结合使用,可以不适用unlock()

    在线程1中:
    std::lock(mutex1,mutex2);
    std::lock_guard<std::mutex> sbguard1(my_mutex1,std::adopt_lock());
    std::lock_guard<std::mutex> sbguard2(my_mutex2,std::adopt_lock());

    在线程2中:
    std::lock(mutex1,mutex2);
    std::lock_guard<std::mutex> sbguard1(my_mutex1,std::adopt_lock());
    std::lock_guard<std::mutex> sbguard2(my_mutex2,std::adopt_lock());
    std::adopt_lock()是一个类对象,表示不需要对std::lock_guard<std::mutex> sbguard1(my_mutex1,std::adopt_lock())中的my_mutex1再次lock()

     1 //使用lock()类模板和lock_guard()解决上述死锁的问题
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <list>
     5 #include <mutex>     //lock()
     6 
     7 using namespace std;
     8 
     9 class A
    10 {
    11 public:
    12     //把收到的信息(玩家命令)放入到一个队列的线程
    13     void inMsgRecvQueue()
    14     {
    15         for (int i = 0; i < 100000; i++)
    16         {
    17             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    18             std::lock(my_mutex1, my_mutex2);
    19             std::lock_guard<std::mutex> sbguard1(my_mutex1,std::adopt_lock);
    20             std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
    21             //中间可能隔了很多代码(需要保护不同的数据块)
    22             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    23             //my_mutex1.unlock();          //只有在对msgRecvQueue写数据的这一句需要保护,其余不需要保护,如果把for循环全保护,那么效率就会降低
    24             //my_mutex2.unlock();
    25         }
    26     }
    27     bool outMsgLULProc(int &command)
    28     {
    29         if (!msgRecvQueue.empty())
    30         {
    31             //消息不为空
    32             //my_mutex1.lock();  //这里必须和inMsgRecvQueue()中锁my_mutex1和my_mutex2的顺序是一样的
    33             //my_mutex2.lock();
    34             std::lock(my_mutex1, my_mutex2);
    35             std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
    36             std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
    37             command = msgRecvQueue.front();
    38             msgRecvQueue.pop_front();  //移除取出的元素
    39             //以下可以考虑处理数据...
    40             //my_mutex1.unlock();          //使用lock_guard就不必再使用lock()和unlock()
    41             //my_mutex2.unlock();
    42             return true;
    43         }
    44         else
    45         {
    46             //先解锁哪个都是可以的
    47             //my_mutex1.unlock();        //使用lock_guard就不必再使用lock()和unlock()
    48             //my_mutex2.unlock();
    49             return false;
    50         }
    51     }
    52 
    53     //把数据从消息队列中取出的线程
    54     void outMsgRecvQueue()
    55     {
    56         int command = 0;
    57 
    58         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    59         {
    60             bool result = outMsgLULProc(command);
    61             if (result == true)
    62             {
    63                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    64                 //以下可以考虑处理数据
    65             }
    66         }
    67     }
    68 
    69 private:
    70     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    71     std::mutex my_mutex1;  //创建一个互斥量1
    72     std::mutex my_mutex2;  //创建一个互斥量2
    73 };
    74 int main()
    75 {
    76     A myobja;  //生成类对象
    77     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    78     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    79     myOutMsgObj.join();
    80     myInMsgObj.join();
    81 
    82     system("pause");
    83     return 0;
    84 }
    使用lock()类模板和lock_guard()解决上述死锁的问题

    这个运行是没有问题的

    五、unique_lock()的讲解

    1、unique_lock()取代lock_guard(),unique_lock和lock_guard一样,在它们类的构造函数中加锁,在析构函数中解锁

    unique_lock()是一个类模板,工作中一般使用unique_lock(),但是占用空间比较大
    lock_guard()和unique_lock()都是对互斥量加锁和解锁

     1 //使用unique_lock()类模板和lock_guard()解决上述死锁的问题
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <list>
     5 #include <mutex>     //lock()
     6 
     7 using namespace std;
     8 
     9 class A
    10 {
    11 public:
    12     //把收到的信息(玩家命令)放入到一个队列的线程
    13     void inMsgRecvQueue()
    14     {
    15         for (int i = 0; i < 100000; i++)
    16         {
    17             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    18             std::unique_lock<std::mutex> sbguard1(my_mutex1);
    19             std::unique_lock<std::mutex> sbguard2(my_mutex2);
    20             //中间可能隔了很多代码(需要保护不同的数据块)
    21             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    22         }
    23     }
    24     bool outMsgLULProc(int &command)
    25     {
    26         if (!msgRecvQueue.empty())
    27         {
    28             //消息不为空
    29             std::unique_lock<std::mutex> sbguard1(my_mutex1);
    30             std::unique_lock<std::mutex> sbguard2(my_mutex2);
    31             command = msgRecvQueue.front();
    32             msgRecvQueue.pop_front();  //移除取出的元素
    33             //以下可以考虑处理数据...
    34             return true;
    35         }
    36         else
    37         {
    38             //先解锁哪个都是可以的
    39             return false;
    40         }
    41     }
    42 
    43     //把数据从消息队列中取出的线程
    44     void outMsgRecvQueue()
    45     {
    46         int command = 0;
    47 
    48         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    49         {
    50             bool result = outMsgLULProc(command);
    51             if (result == true)
    52             {
    53                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    54                 //以下可以考虑处理数据
    55             }
    56         }
    57     }
    58 
    59 private:
    60     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    61     std::mutex my_mutex1;  //创建一个互斥量1
    62     std::mutex my_mutex2;  //创建一个互斥量2
    63 };
    64 int main()
    65 {
    66     A myobja;  //生成类对象
    67     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    68     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    69     myOutMsgObj.join();
    70     myInMsgObj.join();
    71 
    72     system("pause");
    73     return 0;
    74 }
    使用unique_lock()类模板和lock_guard()解决死锁的问题

    2、std::adopt_lock 表示前面前面已经为mutex对象加锁,在unique_lock的构造函数中不需要再次加锁

    std::adopt_lock:表示互斥量已经提前被lock了,不需要使用lock_guard()和unique_lock()再次lock了

     1 //使用unique_lock()类模板解决上述死锁的问题
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <list>
     5 #include <mutex>     //lock()
     6 
     7 using namespace std;
     8 
     9 class A
    10 {
    11 public:
    12     //把收到的信息(玩家命令)放入到一个队列的线程
    13     void inMsgRecvQueue()
    14     {
    15         for (int i = 0; i < 100000; i++)
    16         {
    17             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    18             my_mutex1.lock();
    19             my_mutex2.lock();
    20             std::unique_lock<std::mutex> sbguard1(my_mutex1,std::adopt_lock);
    21             std::unique_lock<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
    22             //中间可能隔了很多代码(需要保护不同的数据块)
    23             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    24         }
    25     }
    26     bool outMsgLULProc(int &command)
    27     {
    28         if (!msgRecvQueue.empty())
    29         {
    30             //消息不为空
    31             my_mutex1.lock();
    32             my_mutex2.lock();
    33             std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
    34             std::unique_lock<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
    35             command = msgRecvQueue.front();
    36             msgRecvQueue.pop_front();  //移除取出的元素
    37             //以下可以考虑处理数据...
    38             return true;
    39         }
    40         else
    41         {
    42             //先解锁哪个都是可以的
    43             return false;
    44         }
    45     }
    46 
    47     //把数据从消息队列中取出的线程
    48     void outMsgRecvQueue()
    49     {
    50         int command = 0;
    51 
    52         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    53         {
    54             bool result = outMsgLULProc(command);
    55             if (result == true)
    56             {
    57                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    58                 //以下可以考虑处理数据
    59             }
    60         }
    61     }
    62 
    63 private:
    64     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    65     std::mutex my_mutex1;  //创建一个互斥量1
    66     std::mutex my_mutex2;  //创建一个互斥量2
    67 };
    68 int main()
    69 {
    70     A myobja;  //生成类对象
    71     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    72     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    73     myOutMsgObj.join();
    74     myInMsgObj.join();
    75 
    76     system("pause");
    77     return 0;
    78 }
    使用unique_lock()类模板和lock()解决上述死锁的问题

    3、std::try_to_lock 尝试加锁,如果加锁失败,可以做其他的事情,而不是阻塞住

    引入实例:

     1 #include <iostream>
     2 #include <thread>    //尖括号表示系统头文件
     3 #include <list>
     4 #include <mutex>     //lock()
     5 
     6 using namespace std;
     7 
     8 class A
     9 {
    10 public:
    11     //把收到的信息(玩家命令)放入到一个队列的线程
    12     void inMsgRecvQueue()
    13     {
    14         for (int i = 0; i < 100000; i++)
    15         {
    16             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    17             std::unique_lock<std::mutex> sbguard1(my_mutex1,std::adopt_lock);
    18             //std::unique_lock<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
    19             //中间可能隔了很多代码(需要保护不同的数据块)
    20             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    21         }
    22     }
    23     bool outMsgLULProc(int &command)
    24     {
    25         if (!msgRecvQueue.empty())
    26         {
    27             //消息不为空
    28             std::unique_lock<std::mutex> sbguard1(my_mutex1);
    29             //std::unique_lock<std::mutex> sbguard2(my_mutex2);
    30 
    31             std::chrono::milliseconds dura(20000);  //20000毫秒(20s)
    32             std::this_thread::sleep_for(dura);  //休息20s
    33 
    34             command = msgRecvQueue.front();
    35             msgRecvQueue.pop_front();  //移除取出的元素
    36             //以下可以考虑处理数据...
    37             return true;
    38         }
    39         else
    40         {
    41             //先解锁哪个都是可以的
    42             return false;
    43         }
    44     }
    45 
    46     //把数据从消息队列中取出的线程
    47     void outMsgRecvQueue()
    48     {
    49         int command = 0;
    50 
    51         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    52         {
    53             bool result = outMsgLULProc(command);
    54             if (result == true)
    55             {
    56                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    57                 //以下可以考虑处理数据
    58             }
    59         }
    60     }
    61 
    62 private:
    63     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    64     std::mutex my_mutex1;  //创建一个互斥量1
    65     std::mutex my_mutex2;  //创建一个互斥量2
    66 };
    67 int main()
    68 {
    69     A myobja;  //生成类对象
    70     //由于是先创建的outMsgRecvQueue()线程,所以应该是outMsgRecvQueue()线程先跑起来
    71     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    72     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    73     myOutMsgObj.join();
    74     myInMsgObj.join();
    75 
    76     system("pause");
    77     return 0;
    78 }
    View Code

    问题:

    outMsgRecvQueue()线程在锁住线程my_mutex1后,sleep了20s,此时inMsgRecvQueue()线程被阻塞住,也不能执行

    由于是先创建的outMsgRecvQueue()线程,所以应该是outMsgRecvQueue()线程先跑起来,然后outMsgRecvQueue()把
    my_mutex1先锁起来,然后进入sleep,此时进入inMsgRecvQueue(),但是会被阻塞在my_mutex1互斥量上,即一个线程卡20s
    则另外一个线程也卡20s

    解决方法:使用try_to_lock,这样即使inMsgRecvQueue()没有拿到my_mutex1的锁,也可以做else中的事情,而不是阻塞住

     1 std::unique_lock<std::mutex> sbguard1(my_mutex1,std::try_to_lock);
     2 if (sbguard1.owns_lock())
     3 {
     4     //拿到了锁
     5         msgRecvQueue.push_back(i);
     6     //处理其他代码
     7 }
     8 else
     9 {
    10     //没拿到锁,可以在else里面做一些其他的事情
    11     cout << "inMsgRecvQueue()执行,但没有拿到锁,只能做点其他的事" << endl;
    12 } 
     1 #include <iostream>
     2 #include <thread>    //尖括号表示系统头文件
     3 #include <list>
     4 #include <mutex>     //lock()
     5 
     6 using namespace std;
     7 
     8 class A
     9 {
    10 public:
    11     //把收到的信息(玩家命令)放入到一个队列的线程
    12     void inMsgRecvQueue()
    13     {
    14         for (int i = 0; i < 100000; i++)
    15         {
    16             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    17             std::unique_lock<std::mutex> sbguard1(my_mutex1,std::try_to_lock);
    18             if (sbguard1.owns_lock())
    19             {
    20                 //拿到了锁
    21                 msgRecvQueue.push_back(i);
    22                 //处理其他代码
    23             }
    24             else
    25             {
    26                 //没拿到锁,可以在else里面做一些其他的事情
    27                 cout << "inMsgRecvQueue()执行,但没有拿到锁,只能做点其他的事" << endl;
    28             }
    29         }
    30     }
    31     bool outMsgLULProc(int &command)
    32     {
    33         if (!msgRecvQueue.empty())
    34         {
    35             //消息不为空
    36             std::unique_lock<std::mutex> sbguard1(my_mutex1);
    37             //std::unique_lock<std::mutex> sbguard2(my_mutex2);
    38 
    39             std::chrono::milliseconds dura(1000);  //1000毫秒(1s)
    40             std::this_thread::sleep_for(dura);  //休息1s
    41 
    42             command = msgRecvQueue.front();
    43             msgRecvQueue.pop_front();  //移除取出的元素
    44             //以下可以考虑处理数据...
    45             return true;
    46         }
    47         else
    48         {
    49             //先解锁哪个都是可以的
    50             return false;
    51         }
    52     }
    53 
    54     //把数据从消息队列中取出的线程
    55     void outMsgRecvQueue()
    56     {
    57         int command = 0;
    58 
    59         for (int i = 0; i < 100000; i++)    //加for循环的目的是为了看的更清楚
    60         {
    61             bool result = outMsgLULProc(command);
    62             if (result == true)
    63             {
    64                 cout << "outMsgRecvQueue()执行,取出一个元素:" << command << endl;
    65                 //以下可以考虑处理数据
    66                 cout << "helloWorld" << endl;
    67             }
    68         }
    69     }
    70 
    71 private:
    72     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    73     std::mutex my_mutex1;  //创建一个互斥量1
    74     std::mutex my_mutex2;  //创建一个互斥量2
    75 };
    76 int main()
    77 {
    78     A myobja;  //生成类对象
    79     //由于是先创建的outMsgRecvQueue()线程,所以应该是outMsgRecvQueue()线程先跑起来
    80     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    81     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    82     myOutMsgObj.join();
    83     myInMsgObj.join();
    84 
    85     system("pause");
    86     return 0;
    87 }
    88 
    89 //outMsgRecvQueue()拿到锁的机会可能比较小
    try_to_lock并判断是否拿到了锁

     执行很多次inMsgRecvQueue()中else语句中的东西,才会执行一次outMsgLULProc()

     4、std::defer_lock 初始化一个不加锁的mutex对象,接下来可以使用unique_lock类对象的成员函数lock()对mutex对象加锁

    自己前面也不可以先lock()
    std::defer_lock就是不给mymutex1加锁,初始化一个没有加锁的mutex,
    没有加锁的mutex的作用是可以灵活的调用unique_lock()的成员函数

    std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);  //只是初始化my_mutex1,将unique_lock类对象sbguard1和my_mutex1绑定,但并不给my_mutex1加锁
    sbguard1.lock(); //此时可以不用unlock(),但是加上也没错,更加的灵活而已

     5、unique_lock()的成员函数

    (1)lock()和unlock()

    1 std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);  //创建一个没有加锁的my_mutex1,只是将sbguard1和my_mutex1绑定
    2 sbguard1.lock();  //此时不用unlock()

    但是也有sbguard1.unlock();因为有一些非共享代码,此时就可以使用sbguard1.unlock()这样会更加灵活一些

    如:

     1 std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);  //创建一个没有加锁的my_mutex1
     2 sbguard1.lock();  //此时不用unlock(),但是加上也可以,目的是可以处理一些非共享代码
     3 //...处理共享数据
     4 sbguard1.unlock(); 
     5 
     6 //...处理非共享数据
     7 
     8 //处理非共享数据完毕,再次lock()住
     9 sbguard1.lock();
    10 
    11 //...处理共享数据
    12 
    13 sbguard1.unlock();  //这句额可以不用,因为sbguard1.lock(); 会自动解锁
    14 //lock()住的代码越少,代码执行效率越高

    (2)try_lock()

    直接上使用方法,也是为了提高效率

    1 std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);  //创建一个没有加锁的my_mutex1,但是sbguard1和my_mutex1绑定了
    2 if(sbguard1.try_lock() == true)  //sbguard1.try_lock()表示给my_mutex1尝试加锁
    3 {
    4     //返回true表示拿到锁了
    5 }
    6 else
    7 {
    8     //返回false表示没有拿到锁,可以做一些其他的事情
    9 }

    (3)release()

    std::unique_lock<std::mutex> sbguard1(my_mutex1,std::defer_lock);表示将unique_lock类对象sbguard1和my_mutex1绑定
    而release就是解除这种绑定

    使用方法:

    1 std::unique_lock<std::mutex> sbguard1(my_mutex1); //表示将unique_lock类对象sbguard1和my_mutex1绑定,并且将my_mutex1锁住(lock()住)
    2 std::mutex *ptx = sbguard1.release();  //意思是现在ptx接管了对my_mutex1互斥量的lock()和unlock()操作共享数据
    3 
    4 ptx->unlock();  //此时需要使用ptx将my_mutex1解锁

    6、unique_lock()所有权的传递

    一个unique_lock对象只能和一个mutex对象绑定
    但是unique_lock对象可以把mutex对象的所有权转移
    但是以下转移方法是错误的:

    1 std::unique_lock<std::mutex> sbguard1(my_mutex1); 
    2 std::unique_lock<std::mutex> sbguard2(sbguard1);   //错误!!!

    正确的转移方法:

    1 std::unique_lock<std::mutex> sbguard1(my_mutex1);           //sbguard1拥有my_mutex1的所有权
    2 std::unique_lock<std::mutex> sbguard2(std::move(sbguard1)); //此时sbguard2拥有my_mutex1的所有权

    转移方法二:

    1 //创建一个返回值为unique_lock对象的函数
    2 std::unique_lock<std::mutex> rtn_unique_lock()
    3 {
    4     std::unique_lock<std::mutex> tmpguard(my_mutex1);
    5     return tempguard;  //从函数返回一个局部的unique_lock对象是可以的,系统会调用unique_lock的移动构造函数,生成临时的tempguard对象
    6 }
    7 std::unique_lock<std::mutex> sbguard1=rtn_unique_lock();  //也相当于sbguard1与my_mutex1绑定

    六、单例设计模式共享数据分析、解决、call_once

    1、设计模式概述 

    设计模式的一些写法和平常代码写法不一样,别人不易看懂
    是国外开发人员为大型开发项目而创建的,一般讲该项目划分为多个模块

    2、单例设计模式---单例类

    整个项目中,有某个或者某个特殊的类,只创建该类的一个类对象
    单例类:只创建一个类对象的类

    单例模式实现过程如下:
    首先,将该类的构造函数私有化(目的是禁止其他程序创建该类的对象);
    其次,在本类中自定义一个对象(既然禁止其他程序创建该类的对象,就要自己创建一个供程序使用,否则类就没法用,更不是单例);
    最后,提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式)。

    直白的讲就是,你不能用该类在其他地方创建对象,而是通过该类自身提供的方法访问类中的那个自定义对象。

    那么问题的关键来了,程序调用类中方法只有两种方式,①创建类的一个对象,用该对象去调用类中方法;②使用类名直接调用类中方法,格式“类名.方法名()”;
    上面说了,构造函数私有化后第一种情况就不能用,只能使用第二种方法。
    而使用类名直接调用类中方法,类中方法必须是静态的,而静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态的。
    这就是单例模式唯一实例必须设置为静态的原因。

     1 /*
     2 单例类中的方法必须是静态方法的原因:
     3 程序调用类中方法只有两种方式,①创建类的一个对象,用该对象去调用类中方法;②使用类名直接调用类中方法,格式“类名::方法名()”;
     4 上面说了,构造函数私有化后第一种情况就不能用,只能使用第二种方法。
     5 而使用类名直接调用类中方法,类中方法必须是静态的,而静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态的。
     6 */
     7 
     8 
     9 #include <iostream>
    10 
    11 using namespace std;
    12 
    13 class MyClass  //这是一个单例类
    14 {
    15 private:
    16     MyClass() {};  //私有化构造函数,则在类外不可以通过MyClass m1;的方式类创建类对象m1
    17 private:
    18     static MyClass *m_instance;  //静态成员变量
    19 public:
    20     static MyClass *GetInstance()
    21     {
    22         if (m_instance == NULL)  //这个if保证每次返回的都是同一个类对象的指针
    23         {
    24             m_instance = new MyClass();
    25             static CGarhuishou c1;   //因为c1为static类型,所以c1的生命周期为整个代码运行时间,当代码运行结束时,会自动调用c1的构造函数,也就delete掉了m_instance
    26         }
    27         return m_instance;
    28     }
    29 
    30     class CGarhuishou  //类中套类,用来释放MyClass类对象
    31     {
    32     public:
    33         ~CGarhuishou()
    34         {
    35             if (MyClass::m_instance)  //如果m_instance被初始化了
    36             {
    37                 delete m_instance;
    38                 MyClass::m_instance = NULL;
    39             }
    40         }
    41     };
    42     void func()
    43     {
    44         cout << "测试" << endl;
    45     }
    46 
    47 };
    48 
    49 //类静态变量初始化
    50 MyClass *MyClass::m_instance = NULL;  //MyClass *是m_instance的类型说明符
    51 
    52 int main()
    53 {
    54     MyClass *p_a = MyClass::GetInstance();  //创建一个类对象,GetInstance()返回的是一个MyClass类对象的地址
    55     MyClass *p_b = MyClass::GetInstance();  //p_a和p_b会指向同一个类对象,这是由于GetInstance()中的if语句判断了是否已经创建了MyClass类对象
    56 
    57     system("pause");
    58     return 0;
    59 }
    单例类示例

     3、单例模式下解决不同线程访问共享数据或方法的问题

    需要在我们自己创建的线程(而不是主线程)中来创建MyClass单例类对象
    不能线程可能同时访问GetInstance(),并同时创建类对象m_instance,如果m_instance已经new过了,再次new就会出问题,此时需要对GetInstance()这种成员函数进行互斥的操作,方法如下:

     1 #include <iostream>
     2 #include <thread>
     3 #include <mutex>
     4 
     5 using namespace std;
     6 
     7 std::mutex resource_mutex;  //创建互斥量用来解决两个线程可能会同时执行m_instance = new MyClass();的问题
     8 
     9 class MyClass  //这是一个单例类
    10 {
    11 private:
    12     MyClass() {};  //私有化构造函数,则在类外不可以通过MyClass m1;的方式类创建类对象m1
    13 private:
    14     static MyClass *m_instance;  //静态成员变量
    15 public:
    16     static MyClass *GetInstance()
    17     {
    18         if (m_instance == NULL)    //使用双重检查,如果m_instance被new(创建)过,那么就不会进入这个if循环,也就不会被锁住,以此提高效率
    19         {
    20             std::unique_lock<std::mutex> mymutex(resource_mutex);
    21             //假设线程1和线程2都执行到这里的时候,再假设线程1先执行下去,由于线程1和线程2遇到的是一个互斥量,所以线程2会被阻塞住
    22             //但是这样效率太低了,方法是用双重检查
    23             if (m_instance == NULL)  //这个if保证每次返回的都是同一个类对象的指针
    24             {
    25                 m_instance = new MyClass();
    26                 static CGarhuishou c1;   //因为c1为static类型,所以c1的生命周期为整个代码运行时间,当代码运行结束时,会自动调用c1的构造函数,也就delete掉了m_instance
    27             }
    28         }
    29         return m_instance;
    30     }
    31 
    32     class CGarhuishou  //类中套类,用来释放MyClass类对象
    33     {
    34     public:
    35         ~CGarhuishou()
    36         {
    37             if (MyClass::m_instance)  //如果m_instance被初始化了
    38             {
    39                 delete m_instance;
    40                 MyClass::m_instance = NULL;
    41             }
    42         }
    43     };
    44     void func()
    45     {
    46         cout << "测试" << endl;
    47     }
    48 
    49 };
    50 
    51 //类静态变量初始化
    52 MyClass *MyClass::m_instance = NULL;  //MyClass *是m_instance的类型说明符
    53 
    54 void mythread()
    55 {
    56     cout << "我的线程1开始执行" << endl;
    57     MyClass *p_a = MyClass::GetInstance();  //在线程1中创建单例类对象p_a,
    58     cout << "我的线程1执行完毕" << endl;
    59 }
    60 
    61 int main()
    62 {
    63     MyClass *p_a = MyClass::GetInstance();  //创建一个类对象,GetInstance()返回的是一个MyClass类对象的地址
    64     MyClass *p_b = MyClass::GetInstance();  //p_a和p_b会指向同一个类对象,这是由于GetInstance()中的if语句判断了是否已经创建了MyClass类对象
    65 
    66     p_b->func();
    67 
    68     std::thread myobj1(mythread); //创建线程1,在调用线程函数mythread()的时候会调用GetInstance()
    69     std::thread myobj2(mythread); //创建线程2,线程1和线程2使用同一个入口函数mythread
    70     myobj1.join();  //等待myobj1线程执行完毕
    71     myobj2.join();  //等待myobj2线程执行完毕
    72     //由于两个线程使用同一个入口函数,于是会与两个通路同时执行GetInstance()
    73     //于是对于m_instance = new MyClass(); 这一句可能存在两个线程同时执行的情况,解决方法是使用互斥量
    74 
    75 
    76     system("pause");
    77     return 0;
    78 }
    View Code

    执行结果:

    4、call_once()函数

    功能:call_once(a)能够保证函数a()只被调用一次
    解决单例模式下类对象创建代码可能被执行多次的问题
    或者是解决单例模式下,多线程多次执行单例类对象多次初始化的问题
    具备互斥量的能力,且比互斥量消耗的资源更少
    std::once_flag是一个结构,用来标记a()是否执行
    如果已经调用a()函数,那么std::once_flag设置为"已调用"状态

    即解决下面例程中线程1和线程2都执行CreatInstance()函数的问题

     1 #include <iostream>
     2 #include <thread>
     3 #include <mutex>
     4 
     5 using namespace std;
     6 
     7 std::mutex resource_mutex;  //创建互斥量用来解决两个线程可能会同时执行m_instance = new MyClass();的问题
     8 std::once_flag g_flag;    //定义一个call_once(A)中A()是否成功执行的标识g_flag
     9 
    10 class MyClass  //这是一个单例类
    11 {
    12 private:
    13     MyClass() {};  //私有化构造函数,则在类外不可以通过MyClass m1;的方式类创建类对象m1
    14 private:
    15     static MyClass *m_instance;  //静态成员变量
    16 public:
    17     static void CreatInstance()  //只被调用一次
    18     {
    19         std::chrono::milliseconds dura(20000);
    20         std::this_thread::sleep_for(dura);      //休息20s
    21 
    22         cout << "CreatInstance()被执行了" << endl;
    23 
    24         m_instance = new MyClass();
    25         static CGarhuishou c1;   //因为c1为static类型,所以c1的生命周期为整个代码运行时间,当代码运行结束时,会自动调用c1的构造函数,也就delete掉了m_instance
    26     }
    27     static MyClass *GetInstance()
    28     {
    29         std::call_once(g_flag, CreatInstance);  //加入两个线程都执行到了这里,其中线程1成功执行了CreatInstance(),那么线程了要等线程1执行完毕,
    30         return m_instance;                      //但是此时g_flag标记CreatInstance()已被执行的状态,那么线程2就不会执行CreatInstance()
    31     }
    32 
    33     class CGarhuishou  //类中套类,用来释放MyClass类对象
    34     {
    35     public:
    36         ~CGarhuishou()
    37         {
    38             if (MyClass::m_instance)  //如果m_instance被初始化了
    39             {
    40                 delete m_instance;
    41                 MyClass::m_instance = NULL;
    42             }
    43         }
    44     };
    45     void func()
    46     {
    47         cout << "测试" << endl;
    48     }
    49 
    50 };
    51 
    52 //类静态变量初始化
    53 MyClass *MyClass::m_instance = NULL;  //MyClass *是m_instance的类型说明符
    54 
    55 void mythread()
    56 {
    57     cout << "我的线程1开始执行" << endl;
    58     MyClass *p_a = MyClass::GetInstance();  //在线程1中创建单例类对象p_a,
    59     cout << "我的线程1执行完毕" << endl;
    60 }
    61 
    62 int main()
    63 {
    64     MyClass *p_a = MyClass::GetInstance();  //创建一个类对象,GetInstance()返回的是一个MyClass类对象的地址
    65     MyClass *p_b = MyClass::GetInstance();  //p_a和p_b会指向同一个类对象,这是由于GetInstance()中的if语句判断了是否已经创建了MyClass类对象
    66 
    67     p_b->func();
    68 
    69     std::thread myobj1(mythread); //创建线程1,在调用线程函数mythread()的时候会调用GetInstance()
    70     std::thread myobj2(mythread); //创建线程2,线程1和线程2使用同一个入口函数mythread
    71     myobj1.join();  //等待myobj1线程执行完毕
    72     myobj2.join();  //等待myobj2线程执行完毕
    73     //由于两个线程使用同一个入口函数,于是会与两个通路同时执行GetInstance()
    74     //于是对于m_instance = new MyClass(); 这一句可能存在两个线程同时执行的情况,解决方法是使用互斥量
    75 
    76 
    77     system("pause");
    78     return 0;
    79 }
    80 
    81 //假设线程1速度比线程2速度快,那么线程1会先执行mythread(),然后执行GetInstance(),然后执行CreatInstance(),然乎线程1开始睡觉
    82 //在线程1开始睡觉的时候,线程2也是先执行mythread(),然后执行GetInstance(),但是线程2执行到CreatInstance()中的
    83 //std::call_once(g_flag, CreatInstance)会卡在那里,等到线程1执行完CreatInstance()的时候,线程2就不会再执行std::call_once(g_flag, CreatInstance)
    84 //即线程2不会再执行CreatInstance()函数
    85 
    86 //最后最好在主线程中创建单例对象,然后在子线程中使用该单例对象
    使用call_once()让某个函数执行一次

    假设线程1速度比线程2速度快,那么线程1会先执行mythread(),然后执行GetInstance(),然后执行CreatInstance(),然乎线程1开始睡觉
    在线程1开始睡觉的时候,线程2也是先执行mythread(),然后执行GetInstance(),但是线程2执行到CreatInstance()中的
    std::call_once(g_flag, CreatInstance)会卡在那里,等到线程1执行完CreatInstance()的时候,线程2就不会再执行std::call_once(g_flag, CreatInstance)
    即线程2不会再执行CreatInstance()函数

    最后最好在主线程中创建单例对象,然后在子线程中使用该单例对象

    七、条件变量 std::condition_varianle、wait()、notify_one()

    1、提高效率的方法--

    (1)双重判断以提高效率

     1 if (!msgRecvQueue.empty())  //如果msgRecvQueue为空,则不执行unique_lock(),也就避免了出现阻塞的情况
     2 {
     3     std::unique_lock<std::mutex> sbguard1(my_mutex1);
     4     std::unique_lock<std::mutex> sbguard2(my_mutex2);
     5     if (!msgRecvQueue.empty())
     6     {
     7         //消息不为空
     8         command = msgRecvQueue.front();
     9         msgRecvQueue.pop_front();  //移除取出的元素
    10         //以下可以考虑处理数据...
    11         return true;
    12     }
    13 }
    14 else
    15 {
    16     std::chrono::millionseconds dura(20000);  //如果线程为空,则睡20s,让其他线程执行
    17     std::this_thread::sleep_for(dura);
    18 }
    双重判断以提高效率

    (2)std::condition_variable类和该类中的wait()、notify_one()方法

    std::condition_variable是一个和条件相关的类类,要和互斥量配合工作
    一般是作为类中的私有变量

    1 std::condition_variable my_cond;  //生成一个条件对象
    2 my_cond.wait(参数1,参数2);

    wait()是std::condition_variable类下的一个成员函数,其中参数2是一个lambda表达式;
    a) 如果第二个参数lambda表达式返回值是true,那么wait()将直接返回,不会阻塞,继续向下执行;
    b) 如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量my_mutex1,并阻塞到本行,阻塞到其他某个线程调用notifuy_one()成员函数为止。当其他线程调      用notify_one()函数则会唤醒out线程的wait()函数(原来wait()函数第二个参数返回值为false,wait()处于睡眠/阻塞状态),wait()就不断的尝试给原来的互斥量加锁,如果获      取不到,那么就会卡在wait()这里等着获取该互斥量加锁如果获取到,那么wait()就走下来了。   
    c) 如果wait()函数没有第二个参数,即my_cond.wait(sbguard1),那么第二个参数(参数2lambda表达式的默认返回值为false),那么wait()将解锁互斥量,并阻塞到本行,        阻塞到其他某个线程执行my_cond.notifuy_one()成员函数为止。当其他线程调用notify_one()函数则会唤醒out线程的wait()函数(原来wait()函数第二个参数返回值为         false,wait()处于睡眠/阻塞状态),wait()就不断的尝试给原来的互斥量加锁,如果获取不到,那么就会卡在wait()这里等着获取该互斥量加锁如果获取到,那么wait()就走     下来了。   

     如果wait()函数加参数2,且被其他线程的notify_one()唤醒之后,wait()的第二个参数返回值还是为false,此时依旧会对互斥量解锁,并阻塞在wait()这里。

        如果wait()不加参数2,那么wait()被其他线程的notify_one()唤醒之后,会无条件的继续向下执行

    代码如下:

     1 //使用condition_variable类和该类对应的成员函数wait()notify_one()解决死锁问题
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <list>
     5 #include <mutex>     //lock()
     6 
     7 using namespace std;
     8 
     9 class A
    10 {
    11 public:
    12     //把收到的信息(玩家命令)放入到一个队列的线程
    13     void inMsgRecvQueue()
    14     {
    15         for (int i = 0; i < 100000; i++)
    16         {
    17             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    18             std::unique_lock<std::mutex> sbguard1(my_mutex1);  //给互斥量my_mutex1加锁,且不用手动解锁
    19             std::unique_lock<std::mutex> sbguard2(my_mutex2);  //线程1执行到这里的时候,如果线程2已经给互斥量my_mutex2加锁,那么线程1就会阻塞在这里
    20             //中间可能隔了很多代码(需要保护不同的数据块)
    21             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    22 
    23             my_cond.notify_one();  //如果out线程中的wait()处于阻塞的状态,那么这一句将会将out线程的wait()唤醒
    24         }
    25     }
    26     //把数据从消息队列中取出的线程
    27     void outMsgRecvQueue()
    28     {
    29         int command = 0;
    30         while (true)
    31         {
    32             std::unique_lock<std::mutex> sbguard1(my_mutex1);
    33             //wait()是std::condition_variable类下的一个成员函数
    34             //如果第二个参数lambda表达式返回值是true,那么wait()将直接返回,不会阻塞,继续向下执行
    35             //如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量my_mutex1,并阻塞到本行
    36             //阻塞到其他某个线程调用notifuy_one()成员函数为止
    37             //如果wait()函数没有第二个参数,即my_cond.wait(sbguard1),那么第二个参数(lambda表达式的默认返回值为false)么wait()将解锁互斥量,并阻塞到本行
    38             //阻塞到其他某个线程调用notifuy_one()成员函数为止(第二个参数返回值为flase的情况)
    39             //
    40             my_cond.wait(sbguard1, [this]{     //一个lambda表达式就是一个可调用对象(函数)
    41                 if (!msgRecvQueue.empty())
    42                     return true;
    43                 else
    44                     return false;
    45                 });
    46             command = msgRecvQueue.front();
    47             msgRecvQueue.pop_front();  //移除取出的元素
    48             sbguard1.unlock();  //unique_lock()可以使用类对象随时unlock,但是unique_lock也可以自动的unlock
    49             cout << "outMsgRecvQueue,取出一个元素:" << command << endl;
    50         }
    51     }
    52 
    53 private:
    54     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    55     std::mutex my_mutex1;  //创建一个互斥量1
    56     std::mutex my_mutex2;  //创建一个互斥量2
    57     std::condition_variable my_cond;  //生成一个条件对象
    58 };
    59 int main()
    60 {
    61     A myobja;  //生成类对象
    62     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    63     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    64     myOutMsgObj.join();
    65     myInMsgObj.join();
    66 
    67     system("pause");
    68     return 0;
    69 }
    70 
    71 /*
    72 一般在主函数中,先创建那个线程,则哪个线程对应的函数先执行,那么在本函数中,outMsgRecvQueue()线程先创建,那么outMsgRecvQueue()先执行
    73 但是也存在inMsgRecvQueue()先执行的情况
    74 那么该代码的执行流程为:
    75 1)执行outMsgRecvQueue()线程,由于该线程是先执行的,所以会先利用unique_lock()给my_mutex1加锁,然后执行wait(),但是此时还没
    76   有执行inMsgRecvQueue()线程,所以msgRecvQueue消息队列为空,则wait()第二个参数返回值为false,outMsgRecvQueue()线程将会阻塞
    77   在wait()这里,并将my_mutex1解锁。
    78 2)系统执行inMsgRecvQueue()线程,由于my_mutex1互斥量是出于解锁状态,所以inMsgRecvQueue()线程会给my_mutex1加锁,然后给
    79   msgRecvQueue添加数字进去,并执行my_cond.notify_one(),执行到inMsgRecvQueue()线程最后,给my_mutex1解锁;
    80 3)由于inMsgRecvQueue()线程调用了notify_one()成员函数,所以outMsgRecvQueue()线程中的wait()将会被唤醒,并给my_mutex1加锁(如果加锁不成功,将会阻塞)
    81   由于此时my_mutex1是处于解锁状态,所以wait()会给my_mutex1加锁成功,且此时msgRecvQueue中是有消息的了,wait()第二个参数返回值为true,outMsgRecvQueue()线程
    82   将会跳过wait(),继续执行接下来的代码。
    83 */
    使用condition_variable类和该类对应的成员函数wait()notify_one()解决死锁问题
    /*
    一般在主函数中,先创建那个线程,则哪个线程对应的函数先执行,那么在本函数中,outMsgRecvQueue()线程先创建,那么outMsgRecvQueue()先执行
    但是也存在inMsgRecvQueue()先执行的情况
    那么该代码的执行流程为:
    1)执行outMsgRecvQueue()线程,由于该线程是先执行的,所以会先利用unique_lock()给my_mutex1加锁,然后执行wait(),但是此时还没
      有执行inMsgRecvQueue()线程,所以msgRecvQueue消息队列为空,则wait()第二个参数返回值为false,outMsgRecvQueue()线程将会阻塞
      在wait()这里,并将my_mutex1解锁。
    2)系统执行inMsgRecvQueue()线程,由于my_mutex1互斥量是出于解锁状态,所以inMsgRecvQueue()线程会给my_mutex1加锁,然后给
      msgRecvQueue添加数字进去,并执行my_cond.notify_one(),执行到inMsgRecvQueue()线程最后,给my_mutex1解锁;
    3)由于inMsgRecvQueue()线程调用了notify_one()成员函数,所以outMsgRecvQueue()线程中的wait()将会被唤醒,并给my_mutex1加锁(如果加锁不成功,将会阻塞)
      由于此时my_mutex1是处于解锁状态,所以wait()会给my_mutex1加锁成功,且此时msgRecvQueue中是有消息的了,wait()第二个参数返回值为true,outMsgRecvQueue()线程
      将会跳过wait(),继续执行接下来的代码。
    */

    执行结果:

    上述程序不好的地方:
    (1)可能in线程执行notify_one()唤醒out线程后,然后又执行了in线程中的unique_lock()并对in线程中的互斥量加锁,导致out线程中的wait()不能重新
        给out线程中的互斥量加锁,结果就是in线程执行了好多次,out线程也执行不了一次。

    (2)假如out线程并没有卡在wait()线程那里,而是去执行别的地方了代码了,那么in线程中执行notify_one()就对线程2中的wait()没有效果
        类似于你在家睡觉,家长能够叫醒你,但是你不在家睡觉,那么家长在家叫你是叫不醒的。


    上述代码的深入思考
    (1)in线程执行的次数较多,但是out线程执行的次数较少,那么需要对in线程进行限流
    (2)原来程序中out线程是先执行in线程后先执行的,那么此时out线程会卡在wait()那里,in线程中的notify_one()会起作用
        但是如果in线程先创建,那么in线程中的notify_one()先执行的话,out线程中的wait()就不会被唤醒,即in线程中的notify_one()没有起到作用

    (3)std::condition_variable类和该类中的wait()、notify_all()方法

    问题的提出:

    in线程中的notify_one()只能唤醒一个线程中的wait(),加入有多个线程中有wait(),但是只有一个线程中有nitify_one(),那么唤醒哪个线程中的wait()是不确定的。但是每个线程中的wait()都有机会被唤醒。

    假如有两个out线程,现在两个out线程中的wait()都需要被唤醒,而in线程中只有一个notify_one(),如果想让in线程唤醒out线程1和out线程2中的wait(),可以在in线程中使用notify_all()。

    例程:

     1 //使用condition_variable类和该类对应的成员函数wait()notify_all()解决死锁问题
     2 #include <iostream>
     3 #include <thread>    //尖括号表示系统头文件
     4 #include <list>
     5 #include <mutex>     //lock()
     6 
     7 using namespace std;
     8 
     9 class A
    10 {
    11 public:
    12     //把收到的信息(玩家命令)放入到一个队列的线程
    13     void inMsgRecvQueue()
    14     {
    15         for (int i = 0; i < 100000; i++)
    16         {
    17             cout << "inMsgRecvQueue执行,插入一个数据:" << i << endl;
    18             std::unique_lock<std::mutex> sbguard1(my_mutex1);  //给互斥量my_mutex1加锁,且不用手动解锁
    19             std::unique_lock<std::mutex> sbguard2(my_mutex2);  //线程1执行到这里的时候,如果线程2已经给互斥量my_mutex2加锁,那么线程1就会阻塞在这里
    20             //中间可能隔了很多代码(需要保护不同的数据块)
    21             msgRecvQueue.push_back(i);  //假设数字i就是收到的玩家命令
    22 
    23             my_cond.notify_all();  //如果out线程中的wait()处于阻塞的状态,那么这一句将会将out线程的wait()唤醒
    24         }
    25     }
    26     //把数据从消息队列中取出的线程
    27     void outMsgRecvQueue()
    28     {
    29         int command = 0;
    30         while (true)
    31         {
    32             std::unique_lock<std::mutex> sbguard1(my_mutex1);
    33             //wait()是std::condition_variable类下的一个成员函数
    34             //如果第二个参数lambda表达式返回值是true,那么wait()将直接返回,不会阻塞,继续向下执行
    35             //如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量my_mutex1,并阻塞到本行
    36             //阻塞到其他某个线程调用notifuy_one()成员函数为止
    37             //如果wait()函数没有第二个参数,即my_cond.wait(sbguard1),那么第二个参数(lambda表达式的默认返回值为false)么wait()将解锁互斥量,并阻塞到本行
    38             //阻塞到其他某个线程调用notifuy_one()成员函数为止(第二个参数返回值为flase的情况)
    39             //
    40             my_cond.wait(sbguard1, [this]{     //一个lambda表达式就是一个可调用对象(函数)
    41                 if (!msgRecvQueue.empty())
    42                     return true;
    43                 else
    44                     return false;
    45                 });
    46             command = msgRecvQueue.front();
    47             msgRecvQueue.pop_front();  //移除取出的元素
    48             cout << "outMsgRecvQueue,取出一个元素:" << command << ",当前线程id=" << std::this_thread::get_id() << endl;
    49             sbguard1.unlock();  //unique_lock()可以使用类对象随时unlock,但是unique_lock也可以自动的unlock
    50         }
    51     }
    52 
    53 private:
    54     std::list<int> msgRecvQueue;  //list容器,专门用来收取玩家发来的命令,即共享数据
    55     std::mutex my_mutex1;  //创建一个互斥量1
    56     std::mutex my_mutex2;  //创建一个互斥量2
    57     std::condition_variable my_cond;  //生成一个条件对象
    58 };
    59 int main()
    60 {
    61     A myobja;  //生成类对象
    62     std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);  //第二个参数是引用,才能保证主线程中的myobja和线程myOutMsgObj中的myobja是同一个类对象
    63     std::thread myOutMsgObj2(&A::outMsgRecvQueue, &myobja);  //线程myOutMsgObj和线程myOutMsgObj2都执行一个函数outMsgRecvQueue()
    64     std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    65     myOutMsgObj.join();
    66     myOutMsgObj2.join();
    67     myInMsgObj.join();
    68     //假如有两个out线程,现在两个out线程中的wait()都需要被唤醒,而in线程中只有一个notify_one(),那么需要对in线程中的notify_one()做更改
    69 
    70     system("pause");
    71     return 0;
    72 }
    使用condition_variable类和该类对应的成员函数wait()notify_all()



  • 相关阅读:
    牛客网 二叉树的镜像 JAVA
    牛客网 反转链表 JAVA
    牛客网 调整数组顺序使奇数位于偶数前面 JAVA
    Integer to Roman LeetCode Java
    Valid Number leetcode java
    Longest Common Prefix
    Wildcard Matching leetcode java
    Regular Expression Matching
    Longest Palindromic Substring
    Add Binary LeetCode Java
  • 原文地址:https://www.cnblogs.com/YiYA-blog/p/12466003.html
Copyright © 2011-2022 走看看