unique_lock详解
unique_lock详解
视频 :https://www.bilibili.com/video/BV1Yb411L7ak?p=8&vd_source=4c026d3f6b5fac18846e94bc649fd7d0
参考文章:https://blog.csdn.net/weixin_42636062/article/details/12006833
unique_lock取代lock_guard
unique_lock
是个类模板,工作中,一般使用lock_guard
(推荐使用!)lock_guard
取代了mutex
的lock()
和unlock()
;unique_lock
比lock_guard
灵活很多,效率上差一点,内存占用多一点。
unique_lock的第二个参数
std::adopt_lock:
std::adopt_lock
:表示这个互斥量已经被lock了(你必须提前把互斥量lock,否则会报异常)std::adopt_lock
标记的效果是“假设调用方线程已经拥有了互斥的所有权(已经lock()成功了);通知lock_guard
不需要在构造函数中lock这个互斥量了
std::try_to_lock
std::try_to_lock
:我们会尝试用mutex
的lock()去锁定这个mutex,但如果没有锁定成功,我们也会立即返回,并不会阻塞在那里;
用这个try_to_lock
的前提是你自己不能先去lock
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <string>
#include <mutex>
using namespace std;
class A {
public:
//把收到的信息(玩家命令) 排到一个队列的线程
void inMsgRecvQueue() {
for (int i = 0; i < 100000; ++i) {
cout << "inMsgRecvQueue()执行,插入一个元素" << endl;
std::unique_lock<std::mutex> sbguard1(my_mutex1, std::try_to_lock);
if (sbguard1.owns_lock()) {
msgRecvQueue.push_back(i); //假设这个数字i就是我收到的命令,我直接弄到消息队列里边
}
else
{
//没拿到锁
cout << "inMsgRecvQueue()执行,但没拿到锁头,只能干点别的事" << i << endl;
}
/*my_mutex2.unlock();
my_mutex1.unlock();*/
}
return;
}
bool outMsgLULProc(int& command) {
//消息不为空
std::lock(my_mutex1, my_mutex2);
std::lock_guard<std::mutex> s1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> s2(my_mutex2, std::adopt_lock);
if (!msgRecvQueue.empty()) { //空不空也算读
//消息不为空
command = msgRecvQueue.front();//返回第一个元素,但是不检查元素是否存在
msgRecvQueue.pop_front(); //移除第一个元素但不返回
return true;
}
return false;
}
//把数据从消息队列中取出的线程
void outMsgRecvQueue() {
int command = 0;
for (int i = 0; i < 100000; ++i) {
bool result = outMsgLULProc(command);
if (result == true) {
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
//可以考虑进行命令(数据)的处理
}
else {
cout << "outMsgRecvQueue()执行,但目前队列消息队列为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; //容器,专门用来代表玩家给咱们发过来的命令。这里就相当于是共享内存
std::mutex my_mutex1;//创建一个互斥量
std::mutex my_mutex2;
};
int main() {
//准备用成员函数作为线程函数的方法来写线程;
A mytobj;
std::thread myOutMsgobj(&A::outMsgRecvQueue, &mytobj); //第二个参数是引用,这样就不是复制一份了,但是就不能用detach了
std::thread myInMsgobj(&A::inMsgRecvQueue, &mytobj);
myOutMsgobj.join();
myInMsgobj.join();//两个谁先谁后无所谓
return 0;
}
std::defer_lock
std::defer_lock
:前提你不能自己先lock(),否则会报异常,意思就是并没有给mutex加锁;初始化了一个没有加锁的mutex。
defer_lock参数
当使用std::defer_lock
参数时,互斥量对象不会立即被加锁,而是延迟到之后的某个时刻再进行加锁操作。
这个特性有时候很有用,特别是当你需要在一段代码中根据条件选择是否需要加锁时。
使用std::defer_lock
的一个典型场景是在使用多个互斥量时,需要在获取所有互斥量的锁之后才能继续执行后续的操作。
lock()和unlock()
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void workWithMutex() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 使用std::defer_lock参数延迟加锁
// 在这里可以进行一些无需加锁的操作
// 根据条件选择加锁
if(/* 满足某个条件 */) {
lock.lock(); // 手动加锁
// 在这里可以安全地访问和修改共享数据
lock.unlock(); // 手动解锁
}
// 继续进行其他操作,无需加锁
// 在这里可以选择再次加锁来访问和修改共享数据
lock.lock(); // 手动加锁
// 在这里可以安全地访问和修改共享数据
lock.unlock(); // 手动解锁
}
int main() {
std::thread t(workWithMutex); // 创建一个线程来执行workWithMutex函数
t.join(); // 等待线程执行完毕
return 0;
}
在这个示例中,我们创建了一个互斥量对象mtx
,并在workWithMutex
函数中使用std::unique_lock
来管理互斥量的加锁和解锁操作。
在创建unique_lock
对象时,我们传入了std::defer_lock
参数,表示互斥量会被延迟加锁,即不会立即加锁。
在workWithMutex
函数中,我们可以在无需加锁的部分进行一些操作。然后,根据某个条件,我们可以选择手动地加锁和解锁互斥量来安全地访问和修改共享数据。
之后,我们又可以选择再次手动加锁和解锁互斥量,以继续访问和修改共享数据。
希望这次的解释更符合您的需求。如果还有其他问题,请随时提问。
try_lock():尝试给互斥量加锁
如果拿不到锁,返回false,否则返回true。
这里直接借用大佬代码
void inMsgRecvQueue(){
for (int i = 0; i < 10000; i++)
{
std::unique_lock<std::mutex> sbguard(my_mutex, std::defer_lock);//没有加锁的my_mutex
if (sbguard.try_lock() == true)//返回true表示拿到锁了
{
msgRecvQueue.push_back(i);
//...
//其他处理代码
}
else
{
//没拿到锁
cout << "inMsgRecvQueue()执行,但没拿到锁头,只能干点别的事" << i << endl;
}
}
}
release()
这里直接借用博主的话,加了下代码
- unique_lock
myUniLock(myMutex);相当于把myMutex和myUniLock绑定在了一起,release()就是解除绑定,返回它所管理的mutex对象的指针,并释放所有权 - mutex* ptx =
myUniLock.release();所有权由ptx接管,如果原来mutex对象处理加锁状态,就需要ptx在以后进行解锁了。
void inMsgRecvQueue() {
for (int i = 0; i < 100000; ++i) {
cout << "inMsgRecvQueue()执行,插入一个元素" << endl;
std::unique_lock<std::mutex> sbguard1(my_mutex1);
std::mutex* ptx = sbguard1.release();//现在你有责任自己解锁mutex1;相当于这个传递就把
//啥都给了你,不仅要你解锁,连锁着的状态也是传递给你的
msgRecvQueue.push_back(i); //假设这个数字i就是我收到的命令,我直接弄到消息队列里边
ptx->unlock();
}
return;
}
unique_lock所有权的传递 (move())
std::unique_lock<mutex> sbguard(my_mutex1)
:所有权概念
sbguard1
拥有my_mutex1
的所有权
但是可以将所有权转移给其他的unique_lock
对象,但不是复制
unique_lock<mutex> aFunction()
{
unique_lock<mutex> myUniLock(myMutex);
//移动构造函数那里讲从函数返回一个局部的unique_lock对象是可以的
//返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
return myUniLock;
}