目录
一、互斥量
上次说到了我们需要保护共享数据
,如何保护?
- 操作的时候,依靠线程A把共享数据锁住,操作数据,解锁
- 其他想要操作共享数据的线程B或C,必须等待线程A解锁,然后B或C将共享数据锁定住,操作数据,解锁
- 互斥量是一个
类对象
理解成一把
锁
,多个线程尝试使用lock()
成员函数来加锁,但是只有一个线程可以锁定成功。
二、互斥量的用法
2.1 lock()和unlock()
线程加锁成功的标志是——lock()
函数成功返回,不然会一直卡在lock()
那里。
- lock()
- unlock()
步骤:
- 先lock()
- 操作共享数据
- unlock()
成对使用,不然会报出异常
忘记unlock()报出异常
2.2 std::lock_guard类模板
如果忘记了unlock(),会有一个类模板lock_guard
,可以直接取代lock()和unlock()
相当于
智能指针
——忘记释放内存没关系,智能指针可以帮你释放
- 不过有了lock_guard()以后,就不能再放lock()和unlock(),不然也会异常
lock_guard不能与lock和unlock共存
为什么二者会冲突呢?
- 因为lock_guard的
构造
函数里执行了mutex::lock()
函数 - 因为lock_guard的
析构
函数里执行了mutex::unlock()
函数
lock_guard类
lock_guard完整类模板
三、死锁
3.1 死锁概念
- 死锁是由至少两个锁,也就是2个互斥量,才能产生的
比如有金锁和银锁,线程A和B
- 线程A执行的时候,线程A先锁了金锁,并成功了,then要去锁银锁
- 出现了上下文切换
- 线程B执行,线程B锁了银锁,并成功了,then要去锁金锁
这样就会产生死锁
——
- 因为线程A拿不到银锁头,所以在一直等待,从而金锁头也会一直被A
lock()
着 - 因为线程B拿不到金锁头,所以也一直等待,从而银锁头也会一直被B
lock()
着 - 从而大家都在等对方释放资源才能执行
代码实现中,因为线程A先调用了mutex1的lock,线程B调用了mutex2的lock,这样就会死锁,解决方案就是二者都先调用mutex1的lock就行。
不过我的代码并没有死锁(停滞),不知道为啥...
但是使用
lock_guard
成功产生了死锁
死锁
可以看到,没执行完就停滞了
3.2 死锁的一般解决方案
- 只要保证2个
互斥量
上锁的顺序一致,就不会死锁
3.3 std::lock()函数模板
这个是用于处理多个互斥量
的
它的能力——可以一次锁住2个或2个以上的互斥量(必须至少2个)
从而,它不存在,因为在多个线程中,因为
锁的顺序导致的死锁问题
std::lock()的用法
这样的好处是不用在乎lock
,但是如果忘记unlock()还是会出问题——
忘记unlock()
3.4 std::lock_guard的std::adopt_lock参数
- std::adopt_lock是一个结构体对象,起一个标记作用
作用就是表示这个互斥量已经lock()过了,不需要在lock_guard的构造函数中再对mutex对象进行lock()了
总结
std::lock()是一次可以锁定多个互斥量的,还是建议一个一个锁
。
博客示例源代码
- lock()和unlock()
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息(玩家命令)放入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++ i )
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();
msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
// 直接把它弄到消息队列中
my_mutex.unlock();
}
return;
}
bool outMsgLULProc(int& command)
{
my_mutex.lock();
if (msgRecvQueue.size())
{
// 消息不为空
int command = msgRecvQueue.front(); // 返回第一个元素
msgRecvQueue.pop_front(); // 移除第一个元素
my_mutex.unlock();
return true;
}
my_mutex.unlock();
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; ++ i )
{
bool res = outMsgLULProc(command);
if (res) cout << "outMsgRecvQueue执行,取出一个元素" << command << endl;
// 如果消息队列为空
else cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给我们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
};
int main()
{
A myobj_a;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);
myInMsgObj.join();
myOutMsgObj.join();
return 0;
}
- std::lock_guard()
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息(玩家命令)放入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++ i )
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();
msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
// 直接把它弄到消息队列中
my_mutex.unlock();
}
return;
}
bool outMsgLULProc(int& command)
{
std::lock_guard<std::mutex> go_guard(my_mutex);
// my_mutex.lock();
if (msgRecvQueue.size())
{
// 消息不为空
int command = msgRecvQueue.front(); // 返回第一个元素
msgRecvQueue.pop_front(); // 移除第一个元素
// my_mutex.unlock();
return true;
}
// my_mutex.unlock();
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; ++ i )
{
bool res = outMsgLULProc(command);
if (res) cout << "outMsgRecvQueue执行,取出一个元素" << command << endl;
// 如果消息队列为空
else cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue; // 容器(消息队列),专门用于代表玩家给我们发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
};
int main()
{
A myobj_a;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);
myInMsgObj.join();
myOutMsgObj.join();
return 0;
}
- 死锁(lock和unlock)
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息(玩家命令)放入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; i ++ )
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex1.lock();
my_mutex2.lock();
msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
// 直接把它弄到消息队列中
my_mutex2.unlock();
my_mutex1.unlock();
}
return;
}
bool outMsgLULProc(int& command)
{
// std::lock_guard<std::mutex> go_guard(my_mutex1);
my_mutex2.lock();
my_mutex1.lock();
if (!msgRecvQueue.empty())
{
// 消息不为空
int command = msgRecvQueue.front(); // 返回第一个元素
msgRecvQueue.pop_front(); // 移除第一个元素
my_mutex1.unlock();
my_mutex2.unlock();
return true;
}
my_mutex1.unlock();
my_mutex2.unlock();
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; i ++ )
{
bool res = outMsgLULProc(command);
if (res == 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 myobj_a;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);
myInMsgObj.join();
myOutMsgObj.join();
}
- 死锁(lock_guard)
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息(玩家命令)放入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; i ++ )
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
/*my_mutex1.lock();
my_mutex2.lock();*/
std::lock_guard<std::mutex> go_guard1(my_mutex1);
std::lock_guard<std::mutex> go_guard2(my_mutex2);
msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
// 直接把它弄到消息队列中
/*my_mutex2.unlock();
my_mutex1.unlock();*/
}
return;
}
bool outMsgLULProc(int& command)
{
std::lock_guard<std::mutex> go_guard2(my_mutex2);
std::lock_guard<std::mutex> go_guard1(my_mutex1);
/*my_mutex2.lock();
my_mutex1.lock();*/
if (!msgRecvQueue.empty())
{
// 消息不为空
int command = msgRecvQueue.front(); // 返回第一个元素
msgRecvQueue.pop_front(); // 移除第一个元素
/*my_mutex1.unlock();
my_mutex2.unlock();*/
return true;
}
/*my_mutex1.unlock();
my_mutex2.unlock();*/
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; i ++ )
{
bool res = outMsgLULProc(command);
if (res == 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 myobj_a;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);
myInMsgObj.join();
myOutMsgObj.join();
}
- std::lock()
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
// 把收到的消息(玩家命令)放入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; i ++ )
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
/*my_mutex1.lock();
my_mutex2.lock();*/
/*std::lock_guard<std::mutex> go_guard1(my_mutex1);
std::lock_guard<std::mutex> go_guard2(my_mutex2);*/
std::lock(my_mutex1, my_mutex2); // 相当于每个互斥量都调用了lock()
msgRecvQueue.push_back(i); // 假设这个数字i就是收到的命令
// 直接把它弄到消息队列中
my_mutex2.unlock();
my_mutex1.unlock();
}
return;
}
bool outMsgLULProc(int& command)
{
/*std::lock_guard<std::mutex> go_guard2(my_mutex2);
std::lock_guard<std::mutex> go_guard1(my_mutex1);*/
/*my_mutex2.lock();
my_mutex1.lock();*/
std::lock(my_mutex1, my_mutex2);
if (!msgRecvQueue.empty())
{
// 消息不为空
int command = msgRecvQueue.front(); // 返回第一个元素
msgRecvQueue.pop_front(); // 移除第一个元素
my_mutex1.unlock();
my_mutex2.unlock();
return true;
}
my_mutex1.unlock();
my_mutex2.unlock();
return false;
}
// 把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; i ++ )
{
bool res = outMsgLULProc(command);
if (res == 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 myobj_a;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj_a);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj_a);
myInMsgObj.join();
myOutMsgObj.join();
}