并发与多线程笔记5

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;

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宛如新生

转发即鼓励,打赏价更高!哈哈。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值