多线程互斥量以及死锁问题解决详解

多线程互斥量以及死锁问题解决详解

1.互斥量(mutex)的基本概念

  互斥量可以理解成一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程可以锁定成功(成功的标志有lock()返回),如果没有锁成功,那么就会在lock()这里不断的尝试。
  #include

2.互斥量的用法

(1)lock(),unlock()

  步骤:先lock()给共享数据加锁,操作共享数据,然后再unlock()解锁。
  规则:lock()和unlock()要成对使用,每调用一次lock(),不然调用一次unlock()。
  解决共享数据访问冲突问题:

#include
#include
#include
#include

class A
{
public:
	//把收到的消息(玩家命令)写入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;

			m_mutex.lock();
			msgRecvQueue.push_back(i);//假设数字i就是收到的命令
			m_mutex.unlock();
		}
	}

	bool outMsgLockPro(int &command)
	{
		m_mutex.lock();
		if (!msgRecvQueue.empty())
		{
			int command = msgRecvQueue.front();
			msgRecvQueue.pop_front();//移除list首元素
			m_mutex.unlock();
			return true;
		}
		m_mutex.unlock();
		return false;
	}

	//把收据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLockPro(command);

			if (result == true)
			{
				std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
				//可以考虑进行命令(数据)处理
			}
			else
			{
				std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl;
			}
		}
		std::cout << "END" << std::endl;
	}

private:
	std::listmsgRecvQueue;//存储玩家发送过来的命令--消息队列
	std::mutex m_mutex;//创建互斥量
};

int main()
{
	A myobj;
	std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj);
	std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj);
	m_outMsgThread.join();
	m_inMsgThread.join();
	
	getchar();
	return 0;
}

(2)std::lock_guard类模板

  为了防止忘记unlock(),引入std::lock_guard的类模板,当忘记unlock()时,std::lock_guard来执行unlock()。
  当使用std::lock_guard类模板时,就不用再使用lock()和unlock()对共享数据进行加锁。
  原理是:lock_guard类构函数中执行了mutex::lock(),而其析构函数中执行了mutex::unlock()。
  缺陷:没有lock()和unlock()灵活。

#include
#include
#include
#include
#include

class A
{
public:
	//把收到的消息(玩家命令)写入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::cout << "inMsgRecvQueue()执行,插入一个元素" << i << std::endl;
			{
				std::lock_guardlockGuard(m_mutex);
				msgRecvQueue.push_back(i);//假设数字i就是收到的命令
			}
			//m_mutex.lock();
			//msgRecvQueue.push_back(i);//假设数字i就是收到的命令
			//m_mutex.unlock();
		}
	}

	bool outMsgLockPro(int &command)
	{
		std::lock_guard lockGuard(m_mutex);
		//m_mutex.lock();
		if (!msgRecvQueue.empty())
		{
			int command = msgRecvQueue.front();
			msgRecvQueue.pop_front();//移除list首元素
			//m_mutex.unlock();
			return true;
		}
		//m_mutex.unlock();
		return false;
	}

	//把收据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLockPro(command);

			if (result == true)
			{
				std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
				//可以考虑进行命令(数据)处理
			}
			else
			{
				std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl;
			}
		}
		std::cout << "END" << std::endl;
	}

private:
	std::listmsgRecvQueue;//存储玩家发送过来的命令--消息队列
	std::mutex m_mutex;//创建互斥量
};

int main()
{
	A myobj;
	std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj);
	std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj);
	m_outMsgThread.join();
	m_inMsgThread.join();

	getchar();
	return 0;
}

3.死锁

(1)死锁演示

  死锁问题是由至少两个锁头也就是两个互斥量(mutex)才能产生。
  死锁案例:如有两互斥量lock1,lock2,且有两个线程A,B。
  ① 在线程A执行的时候,此线程先锁lock1并且成功了,这个时候准备去锁lock2…
  ② 此时,出现了上下文切换,线程B开始执行。这个线程先锁lock2,因为lock2还没有被锁,所以lock2会lock()成功。于是,线程B要去锁lock1…
  ③ 此时,线程A因为拿不到锁lock2,流程走不下去(虽然后面代码有unlock锁lock1的,但是流程走不下去,所以lock1解不开);同理,线程B因为拿不到锁lock1,流程走不下去(虽然后面代码有unlock锁lock2的,但是流程走不下去,所以lock2解不开)。这样,死锁就产生了。
  死锁的实例如下:

#include
#include
#include
#include
#include

class A
{
public:
	//把收到的消息(玩家命令)写入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			m_mutex1.lock();
			m_mutex2.lock();
			msgRecvQueue.push_back(i);//假设数字i就是收到的命令
			m_mutex2.unlock();
			m_mutex1.unlock();
		}
	}

	bool outMsgLockPro(int &command)
	{
		m_mutex2.lock();
		m_mutex1.lock();
		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();//移除list首元素
			m_mutex1.unlock();
			m_mutex2.unlock();
			return true;
		}
		m_mutex1.unlock();
		m_mutex2.unlock();
		return false;
	}

	//把收据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLockPro(command);

			if (result == true)
			{
				std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
				//可以考虑进行命令(数据)处理
			}
			else
			{
				std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl;
			}
		}
		std::cout << "END" << std::endl;
	}

private:
	std::listmsgRecvQueue;//存储玩家发送过来的命令--消息队列
	std::mutex m_mutex1;//创建互斥量
	std::mutex m_mutex2;
};

int main()
{
	A myobj;
	std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj);
	std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj);
	m_outMsgThread.join();
	m_inMsgThread.join();

	getchar();
	return 0;
}

(2)死锁的一般解决方案

  只要保证两个互斥量的上锁顺序一致就不会造成死锁。
  对上面死锁的修正:

#include
#include
#include
#include
#include

class A
{
public:
	//把收到的消息(玩家命令)写入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			m_mutex1.lock();
			m_mutex2.lock();
			msgRecvQueue.push_back(i);//假设数字i就是收到的命令
			m_mutex2.unlock();
			m_mutex1.unlock();
		}
	}

	bool outMsgLockPro(int &command)
	{
		m_mutex1.lock();
		m_mutex2.lock();
		if (!msgRecvQueue.empty())
		{
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();//移除list首元素
			m_mutex2.unlock();
			m_mutex1.unlock();
			return true;
		}
		m_mutex2.unlock();
		m_mutex1.unlock();
		return false;
	}

	//把收据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgLockPro(command);

			if (result == true)
			{
				std::cout << "outMsgRecvQueue()执行,取出一个元素" << command << std::endl;
				//可以考虑进行命令(数据)处理
			}
			else
			{
				std::cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << std::endl;
			}
		}
		std::cout << "END" << std::endl;
	}

private:
	std::listmsgRecvQueue;//存储玩家发送过来的命令--消息队列
	std::mutex m_mutex1;//创建互斥量
	std::mutex m_mutex2;
};

int main()
{
	A myobj;
	std::thread m_outMsgThread(&A::outMsgRecvQueue, &myobj);
	std::thread m_inMsgThread(&A::inMsgRecvQueue, &myobj);
	m_outMsgThread.join();
	m_inMsgThread.join();

	getchar();
	return 0;
}

(3)std::lock()函数模板

  作用:一次锁住两个或者两个以上的互斥量,且不存在再多个线程中,因为锁的顺序问题导致死锁的风险问题。
  std::lock():如果互斥量中有一个没锁住,它就等待所有互斥量都锁住,才继续往下走。特点是:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,而另外一个没锁成功,则立即把已经锁住的解锁。
  std::lock()的应用方式:

	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::lock(m_mutex1, m_mutex2);
			msgRecvQueue.push_back(i);//假设数字i就是收到的命令
			m_mutex2.unlock();
			m_mutex1.unlock();
		}
	}

(4)std::lock_guard()的std::adopt_lock参数

  std::adopt_lock参数作用:表示此互斥量已经lock(),不需要在std::lock_guard构造函数里面再次对对象在此lock()。

	void inMsgRecvQueue()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::lock(m_mutex1, m_mutex2);//防止死锁且自动lock
			std::lock_guardlockGuard1(m_mutex1, std::adopt_lock);//自动解锁且不用再加锁(std::adopt_lock)
			std::lock_guardlockGuard2(m_mutex2, std::adopt_lock);
			msgRecvQueue.push_back(i);//假设数字i就是收到的命令
		}
	}

你可能感兴趣的:(C++并发与多线程)