C++11多线程互斥量用法(一)

C++11多线程互斥量用法(一)

一、创建多个线程

#include <iostream>
#include <thread>
#include <vector>

void print(int inum)
{
	std::cout << "print线程开始执行了, inum =" << inum << std::endl;
}
int main()
{
	std::vector<std::thread> mythreads;

	for (auto i = 0; i < 10; ++i)
	{
		mythreads.push_back(std::thread(print, i));
	}
	for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
	{
		iter->join();
	}
	std::cout << "主线程结束" << std::endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
观察可以发现,并非先创建的线程就先执行,执行的顺序是由操作系统控制的,可能在一个线程未执行完时,时间片时间消耗完,线程停止,多个线程的执行顺序是随机的。

二、多线程只读数据的共享

2.1只读数据的共享

#include	<iostream>
#include	<thread>
#include <vector>

std::vector<int> vec = { 1,2,3 };

void print(int inum)
{
	std::cout << "id 为" << std::this_thread::get_id() 
				   << "的线程 打印vec的数值" << vec[0] << vec[1] << vec[2] << std::endl;
}
int main()
{
	std::vector<std::thread> mythreads;

	for (int i = 0; i < 10; ++i)
	{
		mythreads.push_back(std::thread(print, i));
	}
	for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
	{
		iter->join();
	}
	std::cout << "主线程结束" << std::endl;
	return 0;
}

在这里插入图片描述
可以看到只读数据之间的共享是安全的。

2.2有度有写数据共享

加入同时有线程对数据进行读写,程序肯定会崩溃,这个时候就需要处理,最简单的不崩溃处理,读的时候不能写,两个线程不能同时写,两个线程不能同时读。
互斥量(mutex):是个类对象,理解成一把锁,多个线程尝试用lock()来尝试加锁,只有一个线程可以锁定成功(成功的标志是返回),如果没锁成功,那末流程卡在lock()这里不断尝试去锁这把锁头。
lock()和unlock()必须成对使用

#include <iostream>
#include <thread>
#include <list>
#include <mutex>

class A
{
public:
	void inMsgRecvList()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::cout << "inMsgRecvList()执行插入一个元素" << std::endl;
			//myMutex.lock();
			msgRecvList.push_back(i);
			//myMutex.unlock();
		}
		return;
	}
	bool outMsgRecvMutex(int& command)
	{
		//myMutex.lock();
		if (!msgRecvList.empty())  //注意判断是否为空也是读操作
		{
			int command = msgRecvList.front();  //front只是读取数值并没有删除
			msgRecvList.pop_front();
			//myMutex.unlock();
			return true;
		}
		//myMutex.unlock();
		return false;
	}
	void outMsgRecvList()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool result = outMsgRecvMutex(command);
			if (result)
			{
				std::cout << "outMsgRecvList()执行取出一个命令" << std::endl;
			}
			else
			{
				std::cout << "outMsgRecvList()执行,但消息队列为空" << i<<std::endl;
			}
		}
		std::cout << "end" << std::endl;
	}
protected:
	std::list<int> msgRecvList;  //保存接收到的消息
	std::mutex myMutex;
};

int main()
{
	A obja;
	std::thread myOutMsgObj(&A::outMsgRecvList, std::ref(obja));
	std::thread myInMsgObj(&A::inMsgRecvList, std::ref(obja));

	myOutMsgObj.join();
	myInMsgObj.join();
	return 0;
}

在这里插入图片描述
在未使用互斥量加锁的情况下,程序会异常退出。
在这里插入图片描述
在这里插入图片描述
在加锁之后,程序就可以正常的运行。
在这里插入图片描述
为了避免忘记unlock(),引入了lock_guard的类模板,可以替你unlock();
lock_guard()可以直接取代lock和unlock,使用了lock_guard就不要使用lock和unlock。

std::lock_guard<std::mutex> myGuard(myMutex);//工作原理
/*
	在构造函数中执行了lock()
	在析构函数中执行了unlock()
*/

上述代码可以改为

bool outMsgRecvMutex(int& command)
	{
		std::lock_guard<std::mutex> myguard(myMutex);
		//myMutex.lock();
		if (!msgRecvList.empty())  //注意判断是否为空也是读操作
		{
			int command = msgRecvList.front();  //front只是读取数值并没有删除
			msgRecvList.pop_front();
		//	myMutex.unlock();
			return true;
		}
		//myMutex.unlock();
		return false;
	}
	void inMsgRecvList()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::cout << "inMsgRecvList()执行插入一个元素" << std::endl;
			//加{}提前结束myguard的生命周期就可以减少加锁的时间
			{
				//myMutex.lock();
				std::lock_guard<std::mutex> myguard(myMutex);
				msgRecvList.push_back(i);
				//myMutex.unlock();
			}
		}
		return;
	}

三、死锁问题

3.1 死锁的产生

死锁产生至少有两个互斥量才能产生。
假设存在连个线程A和B,假设两个互斥量C和D
(1)线程A执行的时候,这个线程执行C.lock(),然后去尝试锁D(时间片耗尽出现了切换,D并未加锁成功)
(2)线程B执行的时候,这个线程执行D.lock(),然后去尝试锁C(线程B不断尝试对C加锁)
这个时间就产生了死锁。线程A无法对D加锁不能继续执行,线程B无法对C进行加锁不能继续执行。程序就持续的卡在这里。

使用lock和unlock产生的死锁问题

void inMsgRecvList()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::cout << "inMsgRecvList()执行插入一个元素" << std::endl;
			//加{}提前结束myguard的生命周期就可以减少加锁的时间
			{
				myMutex1.lock();
				myMutex2.lock();
				msgRecvList.push_back(i);
				myMutex1.unlock();
				myMutex2.unlock();
			}
		}
		return;
	}
	bool outMsgRecvMutex(int& command)
	{
		myMutex2.lock();
		myMutex1.lock();
		if (!msgRecvList.empty())  //注意判断是否为空也是读操作
		{
			int command = msgRecvList.front();  //front只是读取数值并没有删除
			msgRecvList.pop_front();
			myMutex1.unlock();
			myMutex2.unlock();
			return true;
		}
		myMutex1.unlock();
		myMutex2.unlock();
		return false;
	}

也可以使用lock_guard

void inMsgRecvList()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::cout << "inMsgRecvList()执行插入一个元素" << std::endl;
			//加{}提前结束myguard的生命周期就可以减少加锁的时间
			{
				{
					std::lock_guard<std::mutex>myGuard1(myMutex1);
					std::lock_guard<std::mutex>myGuard2(myMutex2);
					msgRecvList.push_back(i);
				}
			}
		}
		return;
	}
	bool outMsgRecvMutex(int& command)
	{
		std::lock_guard<std::mutex>myGuard1(myMutex2);
		std::lock_guard<std::mutex>myGuard2(myMutex1);
		if (!msgRecvList.empty())  //注意判断是否为空也是读操作
		{
			int command = msgRecvList.front();  //front只是读取数值并没有删除
			msgRecvList.pop_front();
			return true;
		}
		return false;
	}

在这里插入图片描述
当这样加锁的时候,在某一时刻就会发生死锁的现象。

3.1死锁的一般解决方案

两个互斥量加锁顺序相同

四、std::lock()函数模板

能力:一次锁住两个或者两个以上的互斥量,不存在多个线程加锁顺序而导致死锁风险,要不所有的互斥量都锁住,要不所有的互斥量都没锁。如果只锁了一个,另一个锁不住,他就会去解锁。

4.1 单独使用std::lock()

void inMsgRecvList()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::cout << "inMsgRecvList()执行插入一个元素" << std::endl;
			//加{}提前结束myguard的生命周期就可以减少加锁的时间
			{
				{
					std::lock(myMutex1, myMutex2);
					msgRecvList.push_back(i);
					myMutex1.unlock();
					myMutex2.unlock();
				}
			}
		}
		return;
	}
	bool outMsgRecvMutex(int& command)
	{
		std::lock(myMutex1, myMutex2);
		if (!msgRecvList.empty())  //注意判断是否为空也是读操作
		{
			int command = msgRecvList.front();  //front只是读取数值并没有删除
			msgRecvList.pop_front();
			myMutex1.unlock();
			myMutex2.unlock();
			return true;
		}
		myMutex1.unlock();
		myMutex2.unlock();
		return false;
	}

此时还需要进行手动的unlock

4.2 同时使用std::lock()和std::lock_guard

void inMsgRecvList()
	{
		for (int i = 0; i < 100000; ++i)
		{
			std::cout << "inMsgRecvList()执行插入一个元素" << std::endl;
			//加{}提前结束myguard的生命周期就可以减少加锁的时间
			{
				{
					std::lock(myMutex1, myMutex2);
					std::lock_guard<std::mutex> myGruad1(myMutex1,std::adopt_lock); //加上std::adopt_lock就可不执行构造函数的lock
					std::lock_guard<std::mutex> myGruad2(myMutex2,std::adopt_lock);
					msgRecvList.push_back(i);
				}
			}
		}
		return;
	}
	bool outMsgRecvMutex(int& command)
	{
		std::lock(myMutex1, myMutex2);
		std::lock_guard<std::mutex> myGruad1(myMutex1, std::adopt_lock); //加上std::adopt_lock就可不执行构造函数的lock
		std::lock_guard<std::mutex> myGruad2(myMutex2, std::adopt_lock);
		if (!msgRecvList.empty())  //注意判断是否为空也是读操作
		{
			int command = msgRecvList.front();  //front只是读取数值并没有删除
			msgRecvList.pop_front();
			return true;
		}
		return false;
	}

在std::lock_guard加上adopt_lock表示,已经加锁,不需要再次加锁。然后在生命周期结束的时候,就会调用lock_guard的析构函数进行解锁,就不需要手动解锁。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值