目录
1 互斥量(mutex)的概念
先来说一下目的:保护共享数据,操作时,某个线程用代码把共享数据锁住,其他想操作共享数据的线程必须等待解锁。
互斥量就是一个类对象。理解成一把锁,多个线程尝试用lock()成员函数加锁,只有一个线程能够锁定成功(成功的标志就是lock函数返回)。
互斥量使用要小心,保护数据要合理,保护多了影响效率,保护少了程序崩溃。
给少量代码加锁执行效率高。
2 互斥量(mutex)用法
2.1 lock(), unlock()
步骤:先lock(),确定共享数据上锁范围,然后unlock()
注意:
lock(), unlock()要成对使用。务必谨慎,有lock()而没有unlock(),问题很难排查。
2.2 std::lock_guard类模板
为了防止我们忘记unlock(),引入了一个std::lock_guard的类模板:忘记没事,他给咱填坑。
std::lock_guard类模板,直接取代lock()和unlock().
3 死锁
死锁这个问题是由至少两个互斥量才能产生。
假设有两把锁,金锁和银锁。两个线程A和B。
(1)线程A执行的时候,这个线程先锁金锁,把金锁lock成功了,然后去lock银锁的途中出现了上下文切换(该B粉墨登场了)
(2)线程B执行了,这个线程先锁银锁,因为银锁还没有被锁过,所以银锁可以被lock成功,线程B再去锁金锁。此时此刻,就产生了死锁。
(3)线程A因为拿不到银锁,流程走不下去;线程B也拿不到金锁一样进行不下去。
3.1 std::lock()函数模板
死锁肯定是不行的,写出这样的程序难免会被炒鱿鱼。那么怎么避免思索出现呢?
std::lock()函数模板,用来处理多个互斥量的情况。
能力:一次同时锁住两个及以上的互斥量(至少两个。)
这样就不存在因为在多个线程中的多把锁顺序导致的死锁问题。要么两个互斥量都锁柱,要么都没锁柱。因为它可以达到这样的效果:如果只锁住一个,另一个锁不住,那就把锁住的解锁。
3.2 std::lock()和std::lock_guard成对使用
你以为用std::lock()函数模板就可以完美地避免死锁问题了嘛,事实上他的确是可以了,但是对于容易粗心的我们来说,他还是留下了一个坑容易让我们去踩。
那就是他只管锁却不管解锁!这就要求我们还要知道我们应该拿几把锁,然后对应的去添加几个unlock()解锁。
而,std::lock(my_mutex)std::lock_guard成对使用可以使其具备unlock的能力。
4 示例代码
// project4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <thread>//线程头文件
#include <vector>
#include <list>
#include <mutex>//互斥量头文件
using namespace std;
class A
{
public:
//把收到的消息(玩家指令)入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
// 作用域 出了作用域lock_guard就会执行析构函数
{
std::lock(my_mutex, my_mutex2);
std::lock_guard<std::mutex> sbguard(my_mutex,std::adopt_lock);
std::lock_guard<std::mutex> sbguard(my_mutex2, std::adopt_lock);//这样可以不用考虑unlock()
//std::lock_guard<std::mutex> sbguard(my_mutex);//同时取代lock()和unlock().
//之所以能给我们填坑是因为lock_guard构造函数里执行了my_mutex.lock();析构函数里执行了my_mutex.unlock();
//my_mutex.lock();// 金锁
//my_mutex2.lock();// 银锁
//std::lock(my_mutex, my_mutex);
msgRecvQueue.push_back(i);// 把收到的消息,放进消息队列中。
//my_mutex2.unlock();
//my_mutex.unlock();
}
}
}
bool outMsgState(int &command)
{
//my_mutex2.lock();// 银锁
//my_mutex.lock();// 金锁
std::lock(my_mutex,my_mutex2);
std::lock_guard<std::mutex> sbguard(my_mutex, std::adopt_lock);
std::lock_guard<std::mutex> sbguard(my_mutex2, std::adopt_lock);
if (!msgRecvQueue.empty())
{
cout << "outMsgRecvQueue()执行,取出一个元素 " << endl;
int command = msgRecvQueue.front();//返回第一个元素,在前面取,但不检查元素是否存在。
msgRecvQueue.pop_front();//移除第一个元素,不返回。
//处理数据。
//……
// 这块应尤其注意,相当于程序发生了分支,那么分支里也应该要解锁
//my_mutex.unlock();
//my_mutex2.unlock();
return true;
}
//my_mutex.unlock();
//my_mutex2.unlock();
return false;
}
// 把数据从消息队列中取出的线程。
void outMsgRecvQueue()
{
for (int i = 0; i < 10000; i++)
{
int command;
bool result = outMsgState(command);
if (result)
{
cout << "outMsgState is true, and command is " << command << endl;
}
else
{
cout << "outMsgRecvQueue()执行,但是目前消息队列为空 " << i << endl;
}
}
cout << "end......." << endl;
}
private:
std::list<int> msgRecvQueue;// 容器,专门用于代表晚间给咱们发送过来的指令。
std::mutex my_mutex;// 创建一个互斥量(一把锁)
std::mutex my_mutex2;// 创建一个互斥量(一把锁)
};
int main()
{
vector<thread> mythreads;
A myobja;
thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);//注意,第二个对象要用引用,保证子线程中用的就是主线程提供的对象。
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
cout << "主线程结束!" << endl;
}