【c++多线程】互斥量概念、用法、死锁演示以及unique_lock
第5节 互斥量概念、用法、死锁演示及解决详解
-
(1) 互斥量(mutex)的基本概念
-
(2)互斥量的用法
- (2.1)lock(), unlock()
- (2.2) std::lock_guard类模板
-
(3) 死锁
- (3.1) 死锁演示
- (3.2) 死锁的一般解决方案
- (3.3) std::lock()函数模板
- (3.4) std::lock_guard的std::adopt_lock参数
5.0 线程
这样写因为是有问题的,没有保护线程。
#include <iostream>
#include <list>
#include <thread>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
}
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
if (!this->msgRecvQueue.empty())
{
// 消息不为空
int command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
// 这里考虑处理数据
}
else
{
// 消息队列为空
cout << "outMsgRuecvQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
};
int main(int argc, char *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
5.1 互斥量(mutex)的基本概念
保护共享数据,操作时,某个用代码把共享数据锁住,其他想操作共享数据的线程必须等待解锁(锁定住,操作,解锁)
5.1.1 互斥量(mutex)的基本概念
互斥量是个类对象,理解成一把锁,多个线程尝试用lock()
成员函数来加锁这把锁头,
只有一个线程能锁定成功(成功的标志是lock()
函数返回。
如果没锁成功,那么流程卡在lock()
这里不断的尝试去锁这把锁。
互斥量使用要小心,保护数据不多不少,少了,没达到保护效果,多了,影响效率。
5.2 互斥量用法
5.2.1 lock(),unlock()
步骤:先lock()
,操作共享数据,最后unlock()
:
lock()
和unlock()
要成对使用,有lock
必须要有unlock
,每调用一次lock()
,必然应该调用一次unlock()
。
不应该也不允许调用1次lock()
,却调用2次unlock()
,也不允许调用2次lock()
却调用1次unlock()
,这些非对称数量的调用都会导致程序不稳定甚至崩溃。
加锁最简单版本:
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
my_mutex.lock();
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
my_mutex.unlock();
}
}
bool outMsgLULProc(int &command)
{
my_mutex.lock();
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
my_mutex.unlock(); // 每个分支都要有unlock
return true;
}
my_mutex.unlock(); // 每个分支都要有unlock
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
};
int main(int argc, char *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
5.2.2 std::lock_guard类模板
为了防止忘记unlock()
,引入了一个叫std::lock_guard
的类模板。
和智能指针相似(unique_ptr<>
),忘记释放内存不要紧,智能指针自己会释放。
std::lock_guard
类模板,直接取代lock()
和unlock()
,也就是说,有了lock_guard
之后,再不能使用lock()
和unlock()
这也不用手动unlock
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
std::lock_guard<std::mutex> sbguard(my_mutex);
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
}
}
bool outMsgLULProc(int &command)
{
std::lock_guard<std::mutex> sbguard(my_mutex);
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
使用大括号{}
提前结束锁的周期
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
{
std::lock_guard<std::mutex> sbguard(my_mutex);
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
}
// 其他非共享代码
// .....
}
}
bool outMsgLULProc(int &command)
{
std::lock_guard<std::mutex> sbguard(my_mutex);
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
5.3 死锁
死锁问题的产生至少需要两个互斥量才能产生。
比如:
两个互斥量,mutex1和mutex2
两个线程,A和B
- 线程A执行的时候,这个线程先锁mutex1再锁mutex2
- 出现上下文切换
- 线程B执行了,这个线程先锁mutex2,因为mutex2还没有被锁,所以mutex2会锁成功,线程B要去锁mutex1了。。。
- 这个就出现了死锁
- 线程A因为拿不到mutex2,流程走不下去
- 线程B因为拿不到mutex1,流程走不下去
5.3.1 死锁演示
这样写就会出现死锁
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
my_mutex1.lock(); // 实际工程这两个
my_mutex2.lock();
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
my_mutex2.unlock();
my_mutex1.unlock();
}
}
bool outMsgLULProc(int &command)
{
my_mutex2.lock();
my_mutex1.lock();
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
my_mutex1.unlock();
my_mutex2.unlock();
return true;
}
my_mutex1.unlock();
my_mutex2.unlock();
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
5.3.2 死锁的一般解决方案
只要线程里面锁的顺序一致就不会出现死锁
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
my_mutex1.lock(); // 实际工程这两个
my_mutex2.lock();
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
my_mutex2.unlock();
my_mutex1.unlock();
}
}
bool outMsgLULProc(int &command)
{
my_mutex1.lock();
my_mutex2.lock();
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
my_mutex2.unlock();
my_mutex1.unlock();
return true;
}
my_mutex1.unlock();
my_mutex2.unlock();
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
或者使用std::lock_guard
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
std::lock_guard<std::mutex> sbguard1(my_mutex1);
std::lock_guard<std::mutex> sbguard2(my_mutex2);
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
}
}
bool outMsgLULProc(int &command)
{
std::lock_guard<std::mutex> sbguard1(my_mutex1);
std::lock_guard<std::mutex> sbguard2(my_mutex2);
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
5.3.3 std::lock() 函数模板
能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行)
它不存在这种因为在两个线程中,因为锁的顺序问题导致死锁的风险问题。
std::lock():如果互斥量中有一个没锁住,它就在那里等着,等所有互斥量都锁住,它才能往下走(返回)。
要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另一个没锁成功,则它立即把已经锁住的解锁。
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
std::lock(my_mutex1, my_mutex2); //相当于每个互斥量都调用了.lock()
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
my_mutex2.unlock(); // 前面锁住了两个,所以后面都需要解锁
my_mutex1.unlock();
}
}
bool outMsgLULProc(int &command)
{
//相当于每个互斥量都调用了.lock()
std::lock(my_mutex1, my_mutex2);
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
my_mutex2.unlock(); // 前面锁住了两个,所以后面都需要解锁
my_mutex1.unlock();
return true;
}
my_mutex2.unlock(); // 前面锁住了两个,所以后面都需要解锁
my_mutex1.unlock();
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
5.3.4 std::lock_guard的std::adopt_lock参数
使用lock_guard
主要是为了来解决unlock
的问题。
std::adopt_lock
是个结构体对象,起一个标记作用,作用就是:表示这个互斥量已经lock()
,不需要在std::lock_guard<std::mutex>
里面对mutex对象进行再次lock()
了。
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
std::lock(my_mutex1, my_mutex2); // 相当于每个互斥量都调用了 .lock()
std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock); //这样就不用unlock,因为析构的时候会自动unlock
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
}
}
bool outMsgLULProc(int &command)
{
std::lock(my_mutex1, my_mutex2); // 相当于每个互斥量都调用了 .lock()
std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock); //这样就不用unlock,因为析构的时候会自动unlock
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
总结,std::lock()
:一次锁定多个互斥量,谨慎使用(建议一个一个锁)
第六节 unique_lock详解
- unique_lock 取代lock_quard
- unique_lock的第二个参数
- std::adopt_lock
- std::try_to_lock
- std::defer_lock
- unique_lock的成员函数
- lock()
- unlock()
- try_lock()
- release()
- unique_lock所有权的传递
6.1 unique_lock 取代lock_quard
unique_lock
是个类模板,工作中,一般lock_guard
(推荐使用);lock_guard
取代了mutex
的lock()
和unlock()
;
unique_lock
比lock_guard
灵活很多,效率上差一点,内存占用多一点。
6.2 unique_lock的第二个参数
std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
// adopt_lock标记作用
6.2.1 std::adopt_lock
-
std::adopt_lock:表示这个互斥量已经被lock了(你必须要把互斥量提前lock了,否则会报异常)
-
std::adopt_lock标记的效果就是“假设调用方 线程已经拥有了互斥的所有权(已经lock()成功了)”
-
通知lock_guard不需要在构造函数中lock这个互斥量了;
-
unique_lock也可以带std::adopt_lock标记,含义相同,就是不希望在unique_lock()的构造函数中lock这个mutex
使用adopt_lock
需要加锁
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock); // 使用adopt_lock之前mutex要加锁
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
// 。。。
// 其他处理代码
}
}
bool outMsgLULProc(int &command)
{
my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
出现的情况
线程2中加入延迟,如果这样写,一旦进入线程2,连带线程1也会进行等待,也一样卡住了
所以就需要使用到try_to_lock
(下一节)
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock); // 使用adopt_lock之前mutex要加锁
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
// 。。。
// 其他处理代码
}
}
bool outMsgLULProc(int &command)
{
my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura); // 休息2s
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
6.2.2 std::try_to_lock
尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,也会立即返回,并不会阻塞在那里,用这个 try_to_lock的前提是自己不能先去lock。
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::try_to_lock); // 使用adopt_lock之前mutex要加锁
if (sbguard1.owns_lock())
{
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
// 。。。
// 其他处理代码
}
else
{
cout << "inMsgRecvQueue()执行,没有拿到锁,只能干点别的事情" << i << endl;
}
}
}
bool outMsgLULProc(int &command)
{
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::chrono::milliseconds dura(1000);
std::this_thread::sleep_for(dura); // 休息2s
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
6.2.3 std::defer_lock
-
用这个defer_lock的前提是 你不能自己先lock,否则会报异常。(和
try_to_lock
一样,不能自己lock
,用adopt_lock
需要自己提前加lock
) -
defer_lock的意思 就是 并没有给mutex加锁:初始化一个没有加锁的mutex,但是后面需要对
unique_lock
对象进行加锁 -
我们借着defer_lock的话题,来介绍一些unique_lock的重要成员函数
用刚才有延迟的函数做一下修改,这样改过之后会在线程2那边有等待,也就是线程1会跟着线程2有等待,有阻塞。
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::defer_lock);
sbguard1.lock(); // 这边是对sbguard加锁,不用关心解锁,会自己解锁
// if (sbguard1.owns_lock())
// {
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
// 。。。
// 其他处理代码
// }
// else
// {
// cout << "inMsgRecvQueue()执行,没有拿到锁,只能干点别的事情" << i << endl;
// }
}
}
bool outMsgLULProc(int &command)
{
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::chrono::milliseconds dura(100);
std::this_thread::sleep_for(dura); // 休息2s
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
非阻塞,使用sbguard1.try_lock()
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::defer_lock);
// sbguard1.lock(); // 这边是对sbguard加锁,不用关心解锁,会自己解锁
// 因为用了defer_lock,所以这边bguard可以用try_lock 尝试加锁,和try_to_lock相似
if (sbguard1.try_lock() == true) // 返回true等于拿到锁了
{
// 共享代码,需要加锁的地方
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
// 非共享代码,不需要加锁的,可以先解锁,这个就是unique_lock的灵活性
sbguard1.unlock();
// 非共享代码
// 。。。
// 。。。
// 下面又开始有共享代码了,需要加锁
sbguard1.lock();
// 共享代码
// 。。。。
// ......
// 最后可以解锁,也可以不解锁,因为会自己解锁。
}
else
{
cout << " 没有拿到锁" << endl;
}
}
}
bool outMsgLULProc(int &command)
{
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::chrono::milliseconds dura(100);
std::this_thread::sleep_for(dura); // 休息2s
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
6.3 unique_lock的成员函数
6.3.1 lock()
lock()
加锁
6.3.2 unlock()
unlock 解锁(虽然使用了defer_lock可以不用自己解锁,但是也可以自己去解锁(因为有一些非共享代码要处理,处理完可以在上锁),另外没有上锁的时候不能使用解锁)
比如:
/*
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::defer_lcok); // 没有加锁的my_mutex1
sbguard1.lock(); //咱们不用自己unlock
。。。。 // 处理共享代码
sbguard1.unlock(); // 解锁
。。。。 // 处理非共享代码
sbguard1.lock(); // 上锁
。。。。 // 处理共享代码
// sbguard1.unlock(); // 最后的解锁可有可无
*/
示例代码(会有阻塞):
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::defer_lock);
sbguard1.lock(); // 这边是对sbguard加锁,不用关心解锁,会自己解锁
// 共享代码,需要加锁的地方
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
// 非共享代码,不需要加锁的,可以先解锁,这个就是unique_lock的灵活性
sbguard1.unlock();
// 非共享代码
// 。。。
// 。。。
// 下面又开始有共享代码了,需要加锁
sbguard1.lock();
// 共享代码
// 。。。。
// ......
// 最后可以解锁,也可以不解锁,因为会自己解锁。
}
}
bool outMsgLULProc(int &command)
{
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::chrono::milliseconds dura(100);
std::this_thread::sleep_for(dura); // 休息2s
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
6.3.3 try_lock()
尝试给互斥量加锁,如果拿不到锁,则返回false,如果拿到了锁,返回true,这个函数不会阻塞
6.3.4 release()
返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系。
严格区分unlock()和release()的区别,不要混淆。
如果原来mutex对象处于加锁状态,你有责任接管过来并负责解锁。(release返回的是原始mutex的指针)
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::mutex *ptx = sbguard1.release(); //现在需要自己解锁
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
ptx->unlock(); // 需要自己解锁
}
}
bool outMsgLULProc(int &command)
{
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::chrono::milliseconds dura(100);
std::this_thread::sleep_for(dura); // 休息2s
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
为什么有时候需要unlock(),因为你有lock锁住的代码段越少,执行越快,整个程序运行效率越高
有人也把锁头锁住的代码多少 称为锁的 粒度,粒度一般用粗细来描述;
a)锁住的代码少,这个粒度叫细。执行效率高
b)锁住的代码多,粒度叫粗,那执行效率就低
要学会尽量选择合适粒度的代码来进行保护,粒度太细,可能漏掉共享数据的保护,粒度太粗,影响效率。
选择合适的粒度
6.4 unique_lock所有权的传递
unique_lock
所有权的传递 mutex
,
std::unique_lock<std::mutex> sbguard1(my_mutex1)
; // 所有权权概念
sbguard1
拥有my_mutex1
的所有权
sbguard1
可以把自己对mutex(my_mutex1)
的所有权转移给其他的unique_lock
对象;
所以,unique_lock
对象这个mutex
的所有权 属于 可以转移,但是不能复制。
方法1:std::move()
方法2:return std::unique_lock<std::mutex>
使用move转移所有权:
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::unique_lock<std::mutex> sbguard2(std::move(sbguard1)); // 移动语义,现在相当于sbguard2和my_mutex1绑定到一起
// 现在sbguard1指向空,sbguard2指向my_mutex1
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
}
}
bool outMsgLULProc(int &command)
{
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::chrono::milliseconds dura(100);
std::this_thread::sleep_for(dura); // 休息2s
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}
另一种写法:
#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;
class A
{
public:
std::unique_lock<std::mutex> rtn_unique_lock()
{
std::unique_lock<std::mutex> tmpguard(my_mutex1);
return tmpguard; // 从函数返回一个局部的unique_lock对象是可以的
// 返回这种局部对象 tmpguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数
}
// 把收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
std::unique_lock<std::mutex> sbguard1 = rtn_unique_lock();
this->msgRecvQueue.push_back(i); // 假设这个数字i就是我收到的命令,我直接弄到消息队列里边来
}
}
bool outMsgLULProc(int &command)
{
// my_mutex1.lock();
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::chrono::milliseconds dura(100);
std::this_thread::sleep_for(dura); // 休息2s
if (!this->msgRecvQueue.empty())
{
// 消息不为空
command = this->msgRecvQueue.front(); // 返回第一个元素,但不检查元素是否存在;
this->msgRecvQueue.pop_front(); // 移除第一个元素,但不返回
return true;
}
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
// 用while
while (1)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素 = " << command << endl;
// 进行数据处理
// ....
}
// 当全部读取完之后退出
if (command == 99)
break;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
std::mutex my_mutex1; // 第一把锁
std::mutex my_mutex2; // 第二把锁
};
int main(int argc, char const *argv[])
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); // 成员函数作为线程的入口函数
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
system("pause");
return 0;
}