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

C语言原子引用计数优化实现正确性问询(适配GCC/Clang)

引用计数优化的线程安全性分析

问题说明

给定以下约束,判断这段针对GCC/Clang编写的引用计数优化代码是否正确,并解答两个问题:

  1. 这种减少原子操作的优化是否徒劳?
  2. 不新增原子变量或互斥锁的前提下,能否实现线程安全的同类优化?

约束条件

  • 对象传递到其他线程前,必须递增引用计数并将is_shared设为true;
  • 当对象引用计数变为1时,必须将is_shared设为false;
  • Header_is_unique必须100%线程安全。

待分析代码

#include <stdint.h>

typedef struct Header {
    uint8_t is_shared;
    uint64_t reference_count;
} Header;

void
Header_acquire(
    Header * const self
) {
    if (!self->is_shared) {
        ++self->reference_count;
    } else {
        __atomic_fetch_add(&self->reference_count, 1, __ATOMIC_RELAXED);
    }
}

uint8_t
Header_release(
    Header * const self
) {
    if (!self->is_shared) {
        return !--self->reference_count;
    }

    uint64_t const reference_count =
        __atomic_fetch_sub(&self->reference_count, 1, __ATOMIC_RELEASE);

    if (reference_count == 2) {
        __atomic_thread_fence(__ATOMIC_ACQUIRE);
        self->is_shared = 0;
    }

    return reference_count == 1;
}

uint8_t
Header_is_unique(
    Header const * const self
) {
    return !self->is_shared && self->reference_count == 1;
}

当前代码的问题

这段优化不正确,存在多处线程安全隐患:

1. Header_acquire的竞态条件

当其他线程刚把is_shared从true改为false时,当前线程可能读取到is_shared=0,进而执行普通的++self->reference_count。但此时可能还有其他线程在执行原子递增操作,普通递增和原子操作并发会直接破坏引用计数的正确性,引发数据竞争。

比如线程A完成释放操作,将is_shared设为0;线程B读取到is_shared=0并执行普通递增,同时线程C还在执行原子递增——这会导致引用计数被错误叠加。

2. Header_is_unique的线程安全失效

Header_is_unique直接读取非原子的is_shared和reference_count,完全无法满足“100%线程安全”的约束:

  • 两个变量的读取不是原子操作,可能读到不一致的状态(比如is_shared已被改为0,但reference_count还没被其他线程更新);
  • 没有内存屏障保证可见性,其他线程的修改可能无法及时被当前线程感知,返回错误的唯一性判断结果。

3. Header_release中is_shared的可见性问题

当引用计数从2减到1时,代码用__ATOMIC_ACQUIRE屏障后修改is_shared=0,但这个屏障无法保证其他线程能及时看到is_shared的修改。其他线程读取is_shared时没有对应的屏障,可能一直读到旧值,要么错误地执行普通操作,要么继续用原子操作浪费性能。

优化是否徒劳?

这种优化思路本身不是徒劳的——在单线程独占对象的场景下,用普通操作替代原子操作确实能降低开销、提升性能。但当前实现没有正确处理多线程下的竞态和可见性问题,导致优化无效,甚至引入严重bug。

不新增原子/锁的前提下,能否实现线程安全的同类优化?

可以,但需要严格调整内存操作顺序和内存屏障的使用,贴合GCC/Clang的内存模型,同时遵守给定约束:

核心修正思路

  1. 绑定is_shared与引用计数的状态一致性:is_shared的状态必须完全由引用计数的原子操作结果决定,不能单独修改。比如:

    • 当引用计数从1变为2(第一次被共享到其他线程),必须在原子递增后,配合释放屏障设置is_shared=1;
    • 当引用计数从2变为1(回到独占状态),必须在原子递减后,配合获取屏障设置is_shared=0。
  2. 确保is_shared读取的可见性:所有读取is_shared的操作,必须在原子读取引用计数之后,或者配合获取屏障,确保读到的是最新状态。

  3. 修正Header_is_unique的实现:将两个变量的读取改为原子操作,或者用内存屏障保证读取的原子性和一致性。比如:

    uint8_t Header_is_unique(Header const *const self) {
        __atomic_thread_fence(__ATOMIC_ACQUIRE);
        uint64_t cnt = self->reference_count;
        uint8_t shared = self->is_shared;
        return !shared && cnt == 1;
    }
    

    (注:此示例仅为思路,实际需结合引用计数的原子操作顺序调整)

通过以上调整,可以在不新增原子变量或互斥锁的前提下,实现线程安全的原子操作优化,既保留单线程场景下的性能优势,又保证多线程场景的正确性。


内容的提问来源于stack exchange,提问作者João Pires

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:54:58