实例开篇
线程安全注解是现代C++开发的机制,通常在编译期可以帮助发现一些线程安全问题。
下面直接从实例中来理解。
class Account {
private:Mutex mu;int money GUARDED_BY(mu);void Sub(int amount) {money -= amount; // writing variable 'money' requires holding mutex 'mu' exclusively}void Add(int amount) REQUIRES(mu) {money += amount;}public:void Withdraw(int amount) {mu.Lock();Sub(amount); // warning: mutex 'mu' is still held at the end of function}void Deposit(int amount) {mu.Lock();Add(amount);mu.Unlock();}void TransferFromOthers(Account& b, int amount) {mu.Lock();Sub(amount);b.Add(amount); // calling function 'Add' requires holding mutex 'b.mu' exclusivelymu.Unlock();}
};
这段是一个账户的例子。
接口
向外暴露的接口为:
-
void Withdraw(int amount)取款取款调用了
void Sub(int amount)而这里在
Withdraw函数中也确实在调用Sub函数前已经持有锁mu了,但是这里并未对Sub函数添加REQUIRES(mu)注解,所以Sub函数本身不能保证在此已经持有锁mu,所以触发 Warningmutex 'mu' is still held at the end of function但是在
Sub函数结束后,意味着需要释放锁,否则其他操作将无法获得锁mu,造成死锁问题。 -
void Deposit(int amount)存款存款调用了
void Add(int amount)在进入
Add函数前持有了锁mu,在从Add函数返回后也释放了锁mu,所以无报错。对于
Add函数,其中添加了REQUIRES(mu)注解,如果存在某个调用Add的函数未在进入Add函数前持有锁mu,则会在调用Add函数的部分触发 Warningcalling function 'Add' requires holding mutex 'mu' exclusively。如下代码所示void Deposit(int amount) {Add(amount); // calling function 'Add' requires holding mutex 'mu' exclusively } -
void TransferToOthers(Account& b, int amount)当前账号向账户 b 转账这个操作应该是个原子操作,这里先是减少当前账户余额,然后增加账户 b 的余额。
Sub需要加锁,加的是当前账户的锁mub.Add也需要加锁,加的是账户 b 的锁b.mu但是这里在进入
b.Add函数前,当前函数并未持有锁b.mu,所以触发 Warningcalling function 'Add' requires holding mutex 'b.mu' exclusively
如何修改呢,按照上述所述修改即可
private:Mutex mu;int money GUARDED_BY(mu);void Sub(int amount) REQUIRES(mu) {money -= amount;}void Add(int amount) REQUIRES(mu) {money += amount;}public:void Withdraw(int amount) {mu.Lock();Sub(amount);mu.Unlock();}void Deposit(int amount) {mu.Lock();Add(amount);mu.Unlock();}void TransferToOthers(Account& b, int amount) {mu.Lock();Sub(amount);b.mu.Lock();b.Add(amount);b.mu.Unlock();mu.Unlock();}
};
线程安全注解
这其中涉及到两个注解:
GUARDED_BY(mu),用于标识共享变量的保护互斥量。表示使用该变量时必须获得锁muREQUIRES(mu),用于标识函数在调用时需要独占地持有指定的互斥量。表示进入该函数前必须获得锁mu
在多线程编程中,当多个线程同时访问共享资源时,需要确保某些操作的执行是互斥的,即同一时间只能由一个线程执行。互斥操作通常涉及对共享资源的修改或对多个资源的原子性操作。
Mutex 定义
class LOCKABLE Mutex {
public:Mutex() = default;~Mutex() = default;Mutex(const Mutex&) = delete;Mutex& operator=(const Mutex&) = delete;void Lock() EXCLUSIVE_LOCK_FUNCTION() { mu_.lock(); }void Unlock() UNLOCK_FUNCTION() { mu_.unlock(); }void AssertHeld() ASSERT_EXCLUSIVE_LOCK() {}private:std::mutex mu_;
};
这里涉及到了四个注解:
-
LOCKABLE一个自定义的宏定义或类型定义,表示这是一个可被加锁的类 -
EXCLUSIVE_LOCK_FUNCTION()表示被标记的函数是一个独占锁函数,调用这个函数会获取一个独占锁。 -
UNLOCK_FUNCTION()表示被标记的函数是一个解锁函数,调用这个函数会释放之前获取的独占锁。 -
ASSERT_EXCLUSIVE_LOCK()表示被标记的函数用于断言当前线程持有一个独占锁,确保调用该函数的线程在调用时持有独占锁。
更多的线程安全注解,求STAR!!!
参考
-
线程安全注解——GUARDED_BY
-
【C++11高性能服务器】Thread Safety Analysis,概念,用法详解