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

被折叠的 条评论
为什么被折叠?



