You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

锁实现中如何原子执行不等则递增并采用acquire-release语义?

问题解答

完全可以做到——通过原子操作把「判断锁可用」和「递增计数器」合并成一个原子步骤,再配合acquire-release语义,既能消除竞态保证正确性,又能避免顺序一致语义的全局同步开销,兼顾性能。

原代码的问题根源

原代码里的if(-1 != m_counter)和++m_counter是两个独立操作,中间有空窗期:比如线程A刚判断完计数器不是-1,线程B就把计数器改成了-1,线程A接着执行递增,就会把-1变成0,直接破坏锁的状态。必须把这两步捏成一个不可分割的原子操作,才能解决这个问题。

两种可行实现方式

方式1:用CAS(比较并交换)实现

CAS是原子操作里的常用工具,能帮我们原子地完成「检查当前值是否符合预期,符合就更新」的逻辑:

#include <atomic>

struct Lock
{
    void take_lock()
    {
        std::int8_t expected;
        while (true)
        {
            // 先轻松加载当前值,后续会验证是否有效
            expected = m_counter.load(std::memory_order_relaxed);
            while (true)
            {
                if (expected == -1)
                    break; // 锁被占了,退出内层循环等一等

                // 原子验证并更新:如果当前值还是expected(不是-1),就把它加1
                // 成功时用acquire语义,确保后续临界区能看到之前所有线程的写入
                // 失败时用relaxed就行,因为expected会自动更新成当前实际值
                if (m_counter.compare_exchange_weak(expected, expected + 1,
                    std::memory_order_acquire, std::memory_order_relaxed))
                {
                    return; // 成功拿到锁,直接返回
                }
                // CAS失败的话,expected已经是最新值了,接着循环检查就行
            }

            // 锁不可用时,自旋等它变回来(用relaxed加载,不搞多余同步)
            while (m_counter.load(std::memory_order_relaxed) == -1)
            {
            }
        }
    }

    std::atomic<int8_t> m_counter{0};
};

方式2:用fetch_add原子递增+返回值判断

fetch_add会原子地给计数器加1,同时返回操作前的旧值,我们可以通过旧值判断这次递增是否有效:

#include <atomic>

struct Lock
{
    void take_lock()
    {
        std::int8_t old_val;
        while (true)
        {
            // 原子递增,用acquire语义保证后续操作能看到之前的所有写入
            old_val = m_counter.fetch_add(1, std::memory_order_acquire);
            if (old_val != -1)
            {
                // 旧值不是-1,说明我们递增前锁是可用的,成功拿到锁
                return;
            }
            else
            {
                // 踩坑了:递增前锁已经被设为-1了,得把计数器减回去
                // 用release语义保证这次回滚对其他线程可见
                m_counter.fetch_sub(1, std::memory_order_release);
                // 自旋等锁可用
                while (m_counter.load(std::memory_order_relaxed) == -1)
                {
                }
            }
        }
    }

    std::atomic<int8_t> m_counter{0};
};

为啥选acquire-release语义?

  • acquire语义:成功拿到锁时用,确保后续临界区的代码能看到所有之前线程释放锁时的写入操作,不会出现数据不一致。
  • release语义:回滚错误递增(方式2)或者后续实现解锁时用,确保当前线程的所有修改对其他线程可见。
  • relaxed语义:自旋等待时用,只需要感知计数器的变化,不需要额外的内存同步,能最大化性能。

正确性保证

两种方式都把「判断+递增」做成了原子操作:

  • 方式1通过CAS确保,只有当计数器确实不是-1的时候,才会完成递增,中间不会被其他线程插一脚修改计数器。
  • 方式2通过fetch_add的原子性拿到旧值,一旦发现旧值是-1就回滚,不会留下错误的计数器状态。

这样既解决了原代码的竞态问题,又比顺序一致语义(std::memory_order_seq_cst)少了全局同步的开销,性能更好。

内容的提问来源于stack exchange,提问作者rare77

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 03:16:15