保护共享数据,操作时,某个线程用代码把共享数据锁住、操作数据、解锁、其他想操作共享数据的线程必须等待解锁,锁定住,操作,解锁。
一、互斥量(mutex)的基本概念
- 互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功(成功的标志是lock函数返回了)。如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁这把锁头。
- 互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。
二、互斥量的用法
包含头文件
#include <mutex>
2.1 加锁与解锁
- 步骤: 1. lock() 2. 操作共享数据 3. unlock()
- lock()和unlock()要成对使用
-注意: 对于函数的每一个分支都要有一个unlock解锁。
2.2 lock_guard类模板: 直接取代lock()和unlock()
- lock_guard sbguard(myMutex);取代lock()和unlock()
- lock_guard构造函数执行了mutex::lock();在作用域结束时,调用析构函数,执行mutex::unlock()
- 缺点: 没有lock()和unlock()灵活,lock_guard只能在析构的时候才能解锁。可以加{}括号作为作用域,当大括号结束时候自动析构(在ORB_SLAM2源码中也看到过这种处理)。
#include <memory>
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i) {
std::cout << "inMsgRecvQueue() 执行,插入一个元素" << i << std::endl;
my_mutex.lock();
msgRecvQueue.push_back(i);
my_mutex.unlock();
}
}
bool outMsgLULProc(int &command)
{
my_mutex.lock();
if (!msgRecvQueue.empty()) {
//消息队列不为空
int commond = 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 result = outMsgLULProc(command);
if (result == true)
{
std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
}
else
{
std::cout << "outMsgRecvQueue()执行,但目前消息队列为空" << std::endl;
}
}
}
private:
std::list<int> msgRecvQueue;
std::mutex my_mutex;
};
int main()
{
A mobja;
std::thread myOutnMsgObj(&A::outMsgRecvQueue, &mobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &mobja);
myOutnMsgObj.join();
myInMsgObj.join();
return 0;
}
三、死锁
3.1 死锁的演示
死锁这个问题是至少两个锁头,也就是两个互斥量才能产生。
假设有
mutex1
,
mutex2
;两个线程A,B;
(1) 线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
(2) 线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1;
(3) 此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。。。
3.2 死锁的一般解决方案
只要保证多个互斥量上锁的顺序一样就不会造成死锁。
3.3 std::lock()函数模板
- std::lock(mutex1,mutex2……); 一次锁定多个互斥量(一般这种情况很少),用于处理多个互斥量。
- 如果互斥量中一个没锁住,它就等着,等所有互斥量都锁住,才能继续执行。如果有一个没锁住,就会把已经锁住的释放掉(要么互斥量都锁住,要么都没锁住,防止死锁)
3.4 std::lock_guard的std::adopt_lock参数
-
std::lock_guardstd::mutex my_guard(my_mutex,std::adopt_lock);
加入adopt_lock后,在调用lock_guard的构造函数时,不再进行lock();