能否用refcount.load(std::memory_order_relaxed)==1优化DecRef?直接删除是否安全?
原子引用计数IncRef/DecRef实现疑问解答
现有实现
IncRef
refcount.fetch_add(1, std::memory_order_relaxed)
DecRef
if (refcount.load(std::memory_order_relaxed) == 1 || refcount.fetch_sub(1, std::memory_order_release) == 1) { std::atomic_thread_fence(std::memory_order_acquire); delete this; }
疑问解答
1. 能否用refcount.load(std::memory_order_relaxed) == 1来优化DecRef?
可以做这个前置判断优化,而且当前的DecRef代码已经这么实现了。
这个优化的核心是用更轻量的原子load操作快速判断:如果当前引用计数已经是1,那减1后必然为0,直接进入删除分支即可,省去一次相对更重的fetch_sub原子操作。
需要注意的是,relaxed序的load可能拿到“过时”的值——比如刚load到1,另一个线程就完成了fetch_add把计数改成2,但没关系,后面的fetch_sub会拿到正确的最新值,通过逻辑或的分支依然能保证整体逻辑正确。所以这个优化是安全且有效的。
2. 仅比较引用计数等于1就直接删除对象、不执行release操作是否安全?
绝对不安全,核心原因有两点:
- 内存可见性问题:relaxed序的load没有同步语义,当前线程看到计数为1时,其他线程可能正在执行fetch_add操作,只是结果还没同步到当前线程的缓存里。此时直接删除对象,会导致其他线程还在访问已释放的内存,触发未定义行为。
- 内存同步语义缺失:release操作的作用不只是修改计数,它还能保证当前线程对对象的所有写操作,在delete之前都已刷新到主存,同时和后续的acquire操作建立同步关系。如果跳过release,编译器可能会对指令进行重排,把对象的写操作放到delete之后,导致对象销毁时内部状态还没正确写入,哪怕当前线程真的持有唯一引用,也会出现逻辑错误。
所以无论如何都不能跳过release语义的操作,必须通过fetch_sub(release)或者单独的release屏障来保证内存安全。
内容的提问来源于stack exchange,提问作者Luddes
相关产品推荐
相关产品推荐

