虚幻 – TSharedPtr 实现引用计数时的内存序问题

这里以下面这部分代码为示例进行分析,在虚幻之后的版本中可能改变但大同小异。

FORCEINLINE void AddSharedReference()
{
    SharedReferenceCount.FetchAdd(1, EMemoryOrder::Relaxed);
}

void ReleaseSharedReference()
{
    RefCounter::ValueType OldSharedReferenceCount = SharedReferenceCount.FetchSub(1, EMemoryOrder::Release);

    if (OldSharedReferenceCount == 1)
    {
        AtomicThreadFence(EMemoryOrder::Acquire);

        DestroyObject();

        ReleaseWeakReference();
    }
}

这段代码有三个涉及内存序的行为,分别是自增引用计数,自减引用计数和销毁托管对象。

在更新的虚幻引擎实现中,后两个涉及内存序的行为被合并。

自增引用计数

由于在自增引用计数前后,托管对象一定都处于有效状态,所以这里没有内存序要求。

自减引用计数

在多线程环境下,自减引用计数前即便引用计数不是 1 ,也有可能在之后立马被其他线程自减为 0 。当引用计数为 0 时,意味着托管对象进入无效状态,并且可以在任何线程上销毁托管对象。所以,我们需要确保对托管对象的所有操作不会被重排到自减引用计数操作后面,以免操作无效对象。

综上,这里需要使用 释放 内存序,以指示当前线程中的读或写不能被重排到此存储后,并且当前线程的所有写入,可见于获得该同一原子变量的其他线程。其中其他线程即执行销毁托管对象操作的线程。

销毁托管对象

在多线程环境下,可能在当前线程销毁托管对象时,其他线程刚刚操作过托管对象,其操作并未完全同步至当前线程,如果这时候直接销毁托管对象就可能进入数据竞争未定义行为。

综上,这里需要使用 获得 内存序,以指示当前线程中读或写不能被重排到此加载前。其他释放同一原子变量的线程的所有写入,能为当前线程所见。其中其他线程即刚刚操作过托管对象的线程。

通过以上分析可以看出,这里使用的是典型的 释放获得顺序

发表回复