保护共享数据,操作时,某个线程 用代码把共享数据锁住、操作数据、解锁;其他想操作共享数据的线程必须等待解锁,锁定住,操作,解锁
一、互斥量(mutex)的基本概念
互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功(成功的标志是lock()函数返回),如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。
二、互斥量的用法
包含mutex 头文件(好像也可以不加)
2.1 lock(),unlock()
步骤:1.lock(),2.操作共享数据,3.unlock()。
lock()和unlock()要成对使用,有lock()必然要有unlock,每调用一次lock(),必然应该调用一次unlock();
不应该也不允许调用1次lock()却调用了2次unlock(),也不允许调用2次lock()却调用1次unlock(),这些非对称数量的调用都会导致代码的不稳定甚至崩溃
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
//把收到的消息(玩家命令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgReceiveQueue 执行,插入一个元素" << i << endl;
//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
my_mutex.lock();
msgReceiveQueue.push_back(i);
my_mutex.unlock();
}
return;
}
bool outMsgLULProc(int &command) {
my_mutex.lock();
if (!msgReceiveQueue.empty())
{
//消息不为空,则取出数据
command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
msgReceiveQueue.pop_front(); //移除第一个元素,但不返回
my_mutex.unlock();
return true;
}
my_mutex.unlock();
return false;
}
//把数据从消息队列中取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true) {
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else {
//消息队列为空
cout << "目前消息队列为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
mutex my_mutex;//创建了一个互斥量
};
int main()
{
//用成员函数作为线程的方法来写线程
A myobja;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myInMsgObj.join();
myOutMsgObj.join();
//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
cout << "I Love China" << endl;//最后执行这句,整个进程退出
return 0;
}
2.2 lock_guard类模板
lock_guard guard(myMutex);直接取代lock()和unlock(),也就是说用了lock_guard之后,再不能使用lock()和unlock()了
lock_guard构造函数执行了mutex::lock();在作用域结束时,调用析构函数,执行mutex::unlock()
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
//把收到的消息(玩家命令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgReceiveQueue 执行,插入一个元素" << i << endl;
//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
{
lock_guard<mutex>guard(my_mutex);
msgReceiveQueue.push_back(i);
}
}
return;
}
bool outMsgLULProc(int &command) {
lock_guard<mutex>guard(my_mutex);
if (!msgReceiveQueue.empty())
{
//消息不为空,则取出数据
command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
msgReceiveQueue.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()执行,取出一个元素" << endl;
}
else {
//消息队列为空
cout << "目前消息队列为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
mutex my_mutex;//创建了一个互斥量
};
int main()
{
//用成员函数作为线程的方法来写线程
A myobja;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myInMsgObj.join();
myOutMsgObj.join();
//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
cout << "I Love China" << endl;//最后执行这句,整个进程退出
return 0;
}
三、死锁
3.1 死锁演示
死锁至少有两个互斥量mutex1,mutex2。
a.线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
b.线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1.
c.此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。。。
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
//把收到的消息(玩家命令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgReceiveQueue 执行,插入一个元素" << i << endl;
//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
{
my_mutex1.lock();//实际工程这两个锁头不一定挨着,可能需要保护不同的数据共享块
my_mutex2.lock();
msgReceiveQueue.push_back(i);
my_mutex2.unlock();
my_mutex1.unlock();
}
}
return;
}
bool outMsgLULProc(int &command) {
my_mutex2.lock();//实际工程这两个锁头不一定挨着,可能需要保护不同的数据共享块
my_mutex1.lock();
if (!msgReceiveQueue.empty())
{
//消息不为空,则取出数据
command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
msgReceiveQueue.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 < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true) {
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else {
//消息队列为空
cout << "目前消息队列为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
mutex my_mutex1;//创建了一个互斥量
mutex my_mutex2;
};
int main()
{
//用成员函数作为线程的方法来写线程
A myobja;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myInMsgObj.join();
myOutMsgObj.join();
//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
cout << "I Love China" << endl;//最后执行这句,整个进程退出
return 0;
}
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
//把收到的消息(玩家命令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgReceiveQueue 执行,插入一个元素" << i << endl;
//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
{
lock_guard<mutex>guard1(my_mutex1);
lock_guard<mutex>guard2(my_mutex2);
msgReceiveQueue.push_back(i);
}
}
return;
}
bool outMsgLULProc(int &command) {
lock_guard<mutex>guard2(my_mutex2);
lock_guard<mutex>guard1(my_mutex1);
if (!msgReceiveQueue.empty())
{
//消息不为空,则取出数据
command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
msgReceiveQueue.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()执行,取出一个元素" << endl;
}
else {
//消息队列为空
cout << "目前消息队列为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
mutex my_mutex1;//创建了一个互斥量
mutex my_mutex2;
};
int main()
{
//用成员函数作为线程的方法来写线程
A myobja;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myInMsgObj.join();
myOutMsgObj.join();
//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
cout << "I Love China" << endl;//最后执行这句,整个进程退出
return 0;
}
ps:理论上上面两段代码都会出现死锁现象,但实际并没有。。。
3.2 死锁的一般解决方案:
只要保证多个互斥量上锁的顺序一样就不会造成死锁。
3.3 std::lock()函数模板
std::lock(mutex1,mutex2……); 用来处理多个互斥量才出场
一次锁定多个互斥量(至少两个,多了不限,1个不行)一般这种情况很少。
不存在在多个线程中因为锁的顺序问题导致死锁的风险问题
如果互斥量中一个没锁住,它就等着,等所有互斥量都锁住,才能继续执行。
要么互斥量都锁住,要么都没锁住
如果只锁了一个,另一个没锁成功,则会把已经锁住的解锁
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
//把收到的消息(玩家命令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgReceiveQueue 执行,插入一个元素" << i << endl;
//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
{
lock(my_mutex1, my_mutex2);
msgReceiveQueue.push_back(i);
my_mutex2.unlock();
my_mutex1.unlock();
}
}
return;
}
bool outMsgLULProc(int &command) {
lock(my_mutex1, my_mutex2);
if (!msgReceiveQueue.empty())
{
//消息不为空,则取出数据
command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
msgReceiveQueue.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 < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true) {
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else {
//消息队列为空
cout << "目前消息队列为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
mutex my_mutex1;//创建了一个互斥量
mutex my_mutex2;
};
int main()
{
//用成员函数作为线程的方法来写线程
A myobja;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myInMsgObj.join();
myOutMsgObj.join();
//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
cout << "I Love China" << endl;//最后执行这句,整个进程退出
return 0;
}
3.4 std::lock_guard的std::adopt_lock参数
std::lock_guardstd::mutex my_guard(my_mutex,std::adopt_lock);
加入adopt_lock后,在调用lock_guard的构造函数时,不再进行lock();
adopt_guard为结构体对象,起一个标记作用,表示这个互斥量已经lock(),不需要再std::lock_guardstdstd::mutex构造函数里对对象再次 lock()了。
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
//把收到的消息(玩家命令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgReceiveQueue 执行,插入一个元素" << i << endl;
//数字i即为服务器收到的命令,插入到list中,即收到十万个玩家不断向消息队列中发玩家命令
{
lock(my_mutex1, my_mutex2);
lock_guard<mutex>guard1(my_mutex1,adopt_lock);
lock_guard<mutex>guard2(my_mutex2,adopt_lock);
msgReceiveQueue.push_back(i);
}
}
return;
}
bool outMsgLULProc(int &command) {
lock(my_mutex1, my_mutex2);
lock_guard<mutex>guard2(my_mutex2,adopt_lock);
lock_guard<mutex>guard1(my_mutex1,adopt_lock);
if (!msgReceiveQueue.empty())
{
//消息不为空,则取出数据
command = msgReceiveQueue.front();//返回第一个元素,但不检查元素是否存在
msgReceiveQueue.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()执行,取出一个元素" << endl;
}
else {
//消息队列为空
cout << "目前消息队列为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgReceiveQueue; //list容器,用于代表玩家发送给服务器的命令
mutex my_mutex1;//创建了一个互斥量
mutex my_mutex2;
};
int main()
{
//用成员函数作为线程的方法来写线程
A myobja;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //取对象的地址传参(可以理解为引用)
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myInMsgObj.join();
myOutMsgObj.join();
//程序运行会引发异常,因为没有锁住共享数据,解决这个问题,需要引入互斥量
cout << "I Love China" << endl;//最后执行这句,整个进程退出
return 0;
}
总结:std::lock(); 一次锁定多个互斥量;谨慎使用(建议一个一个锁)