• 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()



  • 相关阅读:
    Sogou C++ Workflow 安装与使用例子
    Ubuntu c++ 使用mysql++ 链接mysql 使用cmake 构建
    现代cmake 从github引入三方库,使用FetchContent ( 3.14 以上版本)
    Vue3 + TypeScript 开发实践总结
    Spring MVC 学习总结(十)——Spring+Spring MVC+MyBatis框架集成(IntelliJ IDEA SSM集成)
    Spring MVC 学习总结(九)——Spring MVC实现RESTful与JSON(Spring MVC为前端提供服务)
    Spring MVC 学习总结(八)——Spring MVC概要与环境配置(IDEA+Maven+Tomcat7+JDK8、示例与视频)
    Spring MVC 学习总结(六)——Spring+Spring MVC+MyBatis框架集成
    Spring MVC 学习总结(五)——校验与文件上传
    Spring MVC 学习总结(四)——视图与综合示例
  • 原文地址:https://www.cnblogs.com/YiYA-blog/p/12466003.html
Copyright © 2020-2023  润新知