一、互斥量的基本概念
- 互斥量:互斥量是个类对象,理解成一把锁(保护共享数据,其他想操作共享数据的线程必须等待解锁)
- 注意:互斥量使用要小心,保护数据不多也不少,少了则没达到保护效果,多了则影响效率
二、互斥量的用法
- 包含头文件
#include <mutex>
1. lock()互斥锁
lock()加锁,unlock() 解锁,两者必须成对使用
class Test
{
public:
Test() {};
~Test() {};
//把收到的消息(玩家命令)入到一个队列的线程
void InQueue()
{
for (int i = 0; i < 100000; ++i)
{
//std::this_thread::sleep_for(std::chrono::milliseconds(1000));
cout << "插入一个元素 : " << i << endl;
mutex.lock();
m_list.push_back(i);
mutex.unlock();
}
}
bool OutMsg(int &nCommand)
{
mutex.lock();
if (!m_list.empty()) //判断是否为空的过程也是一种读,所以也必须加锁
{
nCommand = m_list.front(); //返回第一个元素,但不检查元素是否存在 所以要判断是否为空
m_list.pop_front(); //移除第一个元素,但不返回
//考虑处理数据
mutex.unlock();
return true; //注意返回之前如果加锁了,就必须要解锁
}
mutex.unlock();
return false;
}
//出队列线程函数
void OutQueue()
{
int num = 0;
for (int i = 0; i < 100000; ++i)
{
bool bResult = OutMsg(num);
if (true == bResult)
{
cout << "移除一个元素 : " << num << endl;
}
else
{
cout << "队列中数据为空" << endl;
}
}
}
private:
list<int> m_list; //容器(消息队列),专门用于代表玩家发过来的命令
mutex mutex;
};
int main()
{
Test test;
thread objIn(&Test::InQueue, &test); //第二个参数是引用,才能保证线程里用的是同一个对象
thread objOut(&Test::OutQueue, &test);
objIn.join();
objOut.join();
cout << "I Love China" << endl;
return 0;
}
2.lock_guard()锁
- std::lock_guard类模板:可以直接取代lock()和unlock(), 使用之后就不能再使用lock()和unlock()
- std::lock_guard 原理: 构造函数里执行了mutex::lock(),析构函数里面执行了mutex::unlock();
- 用法:
std::lock_guard<std::mutex> guard(mutex);
bool OutMsg(int &nCommand)
{
lock_guard<std::mutex> guard(mutex);
//mutex.lock();
if (!m_list.empty()) //判断是否为空的过程也是一种读,所以也必须加锁
{
nCommand = m_list.front(); //返回第一个元素,但不检查元素是否存在 所以要判断是否为空
m_list.pop_front(); //移除第一个元素,但不返回
//考虑处理数据
//mutex.unlock();
return true; //注意返回之前如果加锁了,就必须要解锁
}
//mutex.unlock();
return false;
}
可以加 {} 作用域来使guard解锁,提前结束生命周期,调用析构函数
{
lock_guard<std::mutex> guard(mutex);
m_list.push_back(i);
}
三、死锁
1. 死锁的概念
- 死锁的概念:是指两个或者两个以上线程在执行过程中,因争夺资源而产生互相等待的现象,若无外力作用,他们都将无法推进下去,此时,称系统处于死锁
2. 死锁的代码演示
- 死锁代码演示:
class Test
{
public:
Test() {};
~Test() {};
//把收到的消息(玩家命令)入到一个队列的线程
void InQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "插入一个元素 : " << i << endl;
mutex1.lock();
//两个锁不一定是挨着的
mutex2.lock();
m_list.push_back(i);
mutex1.unlock();
mutex2.unlock();
}
}
bool OutMsg(int &nCommand)
{
mutex2.lock();
mutex1.lock();
if (!m_list.empty()) //判断是否为空的过程也是一种读,所以也必须加锁
{
nCommand = m_list.front(); //返回第一个元素,但不检查元素是否存在 所以要判断是否为空
m_list.pop_front(); //移除第一个元素,但不返回
//考虑处理数据
mutex2.unlock();
mutex1.unlock();
return true; //注意返回之前如果加锁了,就必须要解锁
}
mutex2.unlock();
mutex1.unlock();
return false;
}
//出队列线程函数
void OutQueue()
{
int num = 0;
for (int i = 0; i < 100000; ++i)
{
bool bResult = OutMsg(num);
if (true == bResult)
{
cout << "移除一个元素 : " << num << endl;
}
else
{
cout << "队列中数据为空" << endl;
}
}
}
private:
list<int> m_list; //容器(消息队列),专门用于代表玩家发过来的命令
mutex mutex1;
mutex mutex2;
};
int main()
{
Test test;
thread objIn(&Test::InQueue, &test); //第二个参数是引用,才能保证线程里用的是同一个对象
thread objOut(&Test::OutQueue, &test);
objIn.join();
objOut.join();
cout << "I Love China" << endl;
return 0;
}
3. 死锁的解决办法
- 死锁的解决办法:只要两个互斥量加锁顺序保持一致
- std::lock()函数模板:用来处理多个互斥量,能一次锁住两个或两个以上的互斥量(至少两个),它不存在这种因为在多个线程中,因为锁的顺序导致死锁的风险问题;如果互斥量中有一个没锁住,它就在那里等待,等所有互斥量都锁住,它才能往下走(返回);特点:要么两个互斥量都锁住,要么两个都没锁住,如果只锁了一个,另外一个没锁成功,则他立即把已经锁住的解锁
- 用法:
std::lock(mutex1,mutex2); //mutex1 和 mutex2 两者顺序无关
- 代码演示:
class Test
{
public:
Test() {};
~Test() {};
//把收到的消息(玩家命令)入到一个队列的线程
void InQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "插入一个元素 : " << i << endl;
std::lock(mutex1, mutex2);
m_list.push_back(i);
mutex1.unlock();
mutex2.unlock();
}
}
bool OutMsg(int &nCommand)
{
lock(mutex1, mutex2);
if (!m_list.empty()) //判断是否为空的过程也是一种读,所以也必须加锁
{
nCommand = m_list.front(); //返回第一个元素,但不检查元素是否存在 所以要判断是否为空
m_list.pop_front(); //移除第一个元素,但不返回
//考虑处理数据
mutex2.unlock();
mutex1.unlock();
return true; //注意返回之前如果加锁了,就必须要解锁
}
mutex2.unlock();
mutex1.unlock();
return false;
}
//出队列线程函数
void OutQueue()
{
int num = 0;
for (int i = 0; i < 100000; ++i)
{
bool bResult = OutMsg(num);
if (true == bResult)
{
cout << "移除一个元素 : " << num << endl;
}
else
{
cout << "队列中数据为空" << endl;
}
}
}
private:
list<int> m_list; //容器(消息队列),专门用于代表玩家发过来的命令
mutex mutex1;
mutex mutex2;
};
int main()
{
Test test;
thread objIn(&Test::InQueue, &test); //第二个参数是引用,才能保证线程里用的是同一个对象
thread objOut(&Test::OutQueue, &test);
objIn.join();
objOut.join();
cout << "I Love China" << endl;
return 0;
}
- std::lock_guard()的std::adopt_lock参数 : std::adopt_lock是个结构体对象,起一个标记作用:表示这个互斥量已经lock(),不需要在std::lock_guard构造函数里面对对象进行再次lock()了
- 代码演示:
class Test
{
public:
Test() {};
~Test() {};
//把收到的消息(玩家命令)入到一个队列的线程
void InQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "插入一个元素 : " << i << endl;
std::lock(mutex1, mutex2);
std::lock_guard<std::mutex> guard1(mutex1, std::adopt_lock);
std::lock_guard<std::mutex> guard2(mutex2, std::adopt_lock);
m_list.push_back(i);
}
}
bool OutMsg(int &nCommand)
{
lock(mutex1, mutex2);
std::lock_guard<std::mutex> guard1(mutex1, std::adopt_lock);
std::lock_guard<std::mutex> guard2(mutex2, std::adopt_lock);
if (!m_list.empty()) //判断是否为空的过程也是一种读,所以也必须加锁
{
nCommand = m_list.front(); //返回第一个元素,但不检查元素是否存在 所以要判断是否为空
m_list.pop_front(); //移除第一个元素,但不返回
return true; //注意返回之前如果加锁了,就必须要解锁
}
return false;
}
//出队列线程函数
void OutQueue()
{
int num = 0;
for (int i = 0; i < 100000; ++i)
{
bool bResult = OutMsg(num);
if (true == bResult)
{
cout << "移除一个元素 : " << num << endl;
}
else
{
cout << "队列中数据为空" << endl;
}
}
}
private:
list<int> m_list; //容器(消息队列),专门用于代表玩家发过来的命令
mutex mutex1;
mutex mutex2;
};
int main()
{
Test test;
thread objIn(&Test::InQueue, &test); //第二个参数是引用,才能保证线程里用的是同一个对象
thread objOut(&Test::OutQueue, &test);
objIn.join();
objOut.join();
cout << "I Love China" << endl;
return 0;
}