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

 

 



 

posted @ 2020-03-11 22:19  兵临城下的匹夫  阅读(460)  评论(0编辑  收藏  举报
TOP