C++ 线程安全注解

实例开篇

线程安全注解是现代C++开发的机制,通常在编译期可以帮助发现一些线程安全问题。

下面直接从实例中来理解。

cpp 复制代码
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' exclusively
        
        mu.Unlock();
    }
};

这段是一个账户的例子。

接口

向外暴露的接口为:

  • void Withdraw(int amount) 取款

    取款调用了 void Sub(int amount)

    而这里在 Withdraw 函数中也确实在调用 Sub 函数前已经持有锁 mu 了,但是这里并未对 Sub 函数添加 REQUIRES(mu) 注解,所以 Sub 函数本身不能保证在此已经持有锁 mu ,所以触发 Warning mutex '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 函数的部分触发 Warning calling function 'Add' requires holding mutex 'mu' exclusively 。如下代码所示

    cpp 复制代码
    void Deposit(int amount) {
        Add(amount); // calling function 'Add' requires holding mutex 'mu' exclusively
    }
  • void TransferToOthers(Account& b, int amount) 当前账号向账户 b 转账

    这个操作应该是个原子操作,这里先是减少当前账户余额,然后增加账户 b 的余额。

    Sub 需要加锁,加的是当前账户的锁 mu

    b.Add 也需要加锁,加的是账户 b 的锁 b.mu

    但是这里在进入 b.Add 函数前,当前函数并未持有锁 b.mu ,所以触发 Warning calling function 'Add' requires holding mutex 'b.mu' exclusively

如何修改呢,按照上述所述修改即可

cpp 复制代码
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) ,用于标识共享变量的保护互斥量。表示使用该变量时必须获得锁 mu
  • REQUIRES(mu) ,用于标识函数在调用时需要独占地持有指定的互斥量。表示进入该函数前必须获得锁 mu

在多线程编程中,当多个线程同时访问共享资源时,需要确保某些操作的执行是互斥的,即同一时间只能由一个线程执行。互斥操作通常涉及对共享资源的修改或对多个资源的原子性操作。

Mutex 定义

cpp 复制代码
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!!!

参考

相关推荐
rKWP8gKv79 分钟前
Java微服务性能监控:Prometheus与Grafana集成方案
java·微服务·prometheus
老前端的功夫11 分钟前
【Java从入门到入土】28:Stream API:告别for循环的新时代
java·开发语言·python
qq_4352879212 分钟前
第9章 夸父逐日与后羿射日:死循环与进程终止?十个太阳同时值班的并行冲突
java·开发语言·git·死循环·进程终止·并行冲突·夸父逐日
小江的记录本14 分钟前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
yaoxin52112338 分钟前
397. Java 文件操作基础 - 创建常规文件与临时文件
java·开发语言·python
极客先躯3 小时前
高级java每日一道面试题-2025年11月24日-容器与虚拟化题[Dockerj]-runc 的作用是什么?
java·oci 的命令行工具·最小可用·无守护进程·完全标准·创建容器的核心流程·runc 核心职责思维导图
用户60648767188963 小时前
AI 抢不走的技能:用 Claude API 构建自动化工作流实战
java
我命由我123453 小时前
Kotlin 开发 - lateinit 关键字
android·java·开发语言·kotlin·android studio·android-studio·android runtime
aXin_ya3 小时前
微服务第八天 Sentinel 四种分布式事务模式
java·数据库·微服务
BenSmith3 小时前
从零上手嵌入式 RTOS:以 Raspberry Pi Pico 2 WH 为例的烧录、定制构建与多系统对比指南
安全