天天看点

2021/10/4 C++多线程编程(5) 互斥量的概念、用法、死锁演示与解决详解

保护共享数据,操作时,某个线程用代码把共享数据锁住、操作数据、解锁、其他想操作共享数据的线程必须等待解锁,锁定住,操作,解锁。

一、互斥量(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();

c++

继续阅读