直接返回InterlockedDecrementRelease() 的结果和先把结果存入变量再返回之间有区别吗?
我想知道下面这两段代码是否有差异。
我在一个智能引用/指针实现中看到了第一段,作为引用计数的一部分。
int Dec() const
{
int ret = InterlockedDecrementRelease(&_count);
return ret;
}
该方法调用Windows API以原子方式减少 _count(它是一个可变的volatile变量)。它实际上只在Windows的 x86架构上进行编译,因此我推测 InterlockedDecrementRelease() 与 InterlockedDecrement() 相同。
但我想知道它是否会与下面的代码段不同
int Dec() const
{
return InterlockedDecrementRelease(&_count);
}
在普通函数的情况下,我会认为带优化的编译器会生成相同的代码。
但在多线程和涉及屏障的函数中,我不确定中间变量是否有特殊用途,还是仅仅是一种编码风格。
解决方案
完全没有差异。中间变量没有任何意义。多线程和它有什么关系?
"我假设 InterlockedDecrementRelease() 与 InterlockedDecrement() 相同。"
是的,发现并不难:
define InterlockedDecrementRelease _InterlockedDecrement
和
define InterlockedDecrement _InterlockedDecrement
但仅在x86/x64上才可用。
另外,真实的代码通常应当看起来像
struct SomeStruct
{
LONG _count = 1;
LONG AddRef()
{
return InterlockedIncrementNoFence(&_count);
}
LONG Release() // Dec
{
if (LONG ret = InterlockedDecrement(&_count))
{
return ret;
}
delete this;
return 0;
}
};
当Dec/Release使 _count为 0时,我们需要做点什么。通常是删除对象/调用析构函数等。此外Release至少需要memory_order_acq_rel,而不仅仅是memory_order_release。当AddRef() 可以放宽时。
当一个线程调用AddRef() 时,它必须已经对该对象拥有一个有效引用。既然如此,在该线程接收到该指针时,对对象字段的内存同步已经发生。
计数器的自增仅仅是为了防止其他线程删除对象。这里内存可见性的顺序并不重要。
与此同时,Release() 必须与另一个Release保持一致,而不是与AddRef() 保持一致……一个线程在Release() 之前对对象所写的所有内容,在另一个线程的Release() 之后必须对该线程可见。
这就是需要Acquire + Release(即memory_order_acq_rel)的原因。
尽管这是可行的,甚至更好,以下实现将会是
LONG Release()
{
if (int ret = InterlockedDecrementRelease(&_count))
{
return ret;
}
MemoryBarrier();
delete this;
return 0;
}