C++临界区mutex学习记录

本文介绍了C++中使用mutex确保多线程环境下共享数据访问的正确性。通过详细讲解mutex的定义方式、类设计以及经典案例,如避免死锁的策略,展示了如何在实践中有效利用mutex。此外,还提到了C++11的STL提供的更优实现,如lock函数来解决加锁顺序问题。

头文件

#incldue <mutex>

主要用途

在多线程环境下,保证共享数据访问的正确性。

好的定义方式

mutable std::mutex m_mutex; 

本质上mutex是一个值,mutable关键字使得mutex在常函数中也能改变值。

类的设计

如果设计一个类,而这个类是线程共享的资源,那么我们可以在类中加一个锁。如下:

class Test
{
public:
	Test(){}
	~Test(){}
	void lockMutex()
	{
		m_mutex.lock();
	}
	void unlockMutex()
	{
		m_mutex.unlock();
	}
private:
	mutable std::mutex m_mutex;
	int m_id;
}

但是这样设计类,使用起来非常不方便。别人可能会忘记使用unlockMutex()或者重复使用lockMutex(),导致死锁。好的做法是,只在类的内部使用临界区。定义一个Lock类,在构造函数中加锁,在析构时解锁。函数调用的开头创建一个Lock对象,那么在函数调用结束,会自动析构释放锁资源。例如:

template<typename T>
class Lock
{
	public:
	Lock(T &mutex): m_mutex(mutex){m_mutex.lock();}
	~Lock(){m_mutex.unlock();}

	private:
	T& m_mutex;
};

这样简单的设计可以满足一部分需求,而C++11中 STL也有更好实现供我们使用。

std::lock_guard<std::mutex> lock(m_mutex);

经典案例

考虑一个银行转账的场景:从a账户转账100块到b账户,这个过程应该是原子性的。如果给出这样一个转账函数(伪代码)

void transferMoney(BankCount &a, BankCount &b, int money)
{
	1.锁住a;
	2.锁住b;
	3.a.m_money -= money;
	4.b.m_money += monney;
}

在多线程环境下会出现什么问题呢?
假设
在线程1中执行transferMoney(a, b, 100);执行到锁住a
在线程2中执行transferMoney(b, a, 100);执行到锁住b
此时双方都在等对方释放锁,产生死锁!

  • 一个解决方案是:固定加锁顺序,例如先给地址小的加锁。
  • 然而C++11中给了我们更好的解决方案:使用lock函数保证在返回时锁住所有的mutex
std::lock(a.m_mutex, b.m_mutex);
std::lock_guard<std::mutex> lockA(a.m_mutex, std::adopt_lock);
std::lock_guard<std::mutex> lockB(b.m_mutex, std::adopt_lock);

adopt_lock是 unique_lock 或者 lock_guard的一个可能构造参数,表示在当前线程已经加锁,不需要再构造函数中加锁。那么,函数调用结束后,自动调用析构函数解锁。

参考资料

http://www.cplusplus.com/
C++游戏服务器编程:
https://www.bilibili.com/video/BV1At411P7AN?p=66

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值