如何在C ++中实现线程安全引用计数

时间:2020-03-06 14:21:37  来源:igfitidea点击:

如何使用C ++编程语言在X86 CPU上实现高效且线程安全的引用计数系统?

我总是遇到这样的问题:关键操作不是原子操作,并且可用的X86 Interlock操作不足以实现引用计数系统。

以下文章介绍了此主题,但需要特殊的CPU指令:

http://www.ddj.com/architect/184401888

解决方案

如今,我们可以使用Boost / TR1 shared_ptr <>智能指针来保持引用计数。

效果很好;没有大惊小怪,没有糊涂。 shared_ptr <>类负责处理对引用计数的所有锁定。

严格来说,我们需要等到C ++ 0x才能够用纯C ++编写线程安全代码。

现在,我们可以使用Posix,或者围绕比较和交换和/或者互锁的增/减创建自己的平台独立包装器。

在VC ++中,可以使用_InterlockedCompareExchange。

do
   read the count
   perform mathematical operation
   interlockedcompareexchange( destination, updated count, old count)
until the interlockedcompareexchange returns the success code.

在其他平台/编译器上,对MS的_InterlockedCompareExchange公开的LOCK CMPXCHG指令使用适当的内部函数。

如果指令本身不是原子指令,那么我们需要将更新相应变量的代码部分设为关键部分。

即,我们需要通过使用某种锁定方案来防止其他线程进入该代码段。当然,锁必须是原子锁,但是我们可以在pthread_mutex类中找到原子锁机制。

效率问题:pthread库尽可能高效,并且仍然可以确保互斥锁对于OS是原子的。

价格昂贵:可能吧。但是,对于所有需要保证的事情,都有代价。

ddj文章中发布的特定代码增加了额外的复杂性,以解决使用智能指针时的错误。

具体来说,如果我们不能保证智能指针不会在分配给另一个智能指针的过程中发生变化,则说明我们做错了或者开始做的事情很不可靠。如果在将智能指针分配给另一个智能指针时可以更改它,则意味着进行分配的代码不拥有该智能指针,因此怀疑是从此开始。

Win32 InterlockedIncrementAcquire和InterlockedDecrementRelease(如果我们想确保安全并关心可能进行重新排序的平台,因此需要同时发出内存屏障)或者InterlockedIncrement和InterlockedDecrement(如果我们确定将保留x86)是原子的并且做这份工作。

就是说,Boost / TR1 shared_ptr <>将为我们处理所有这些操作,因此,除非我们需要自己实施它,否则我们可能会尽一切努力坚持下去。

请记住,锁定非常昂贵,并且每次在智能指针之间传递对象时都会发生锁定,即使该对象当前由一个线程拥有(智能指针库也不知道)。

鉴于此,这里可能有一个适用的经验法则(很高兴得到纠正!)

如果以下情况适用于我们:

  • 我们具有复杂的数据结构,很难为之编写析构函数(或者在设计上不适合使用STL样式的值语义的情况),因此我们需要智能指针才能为我们完成此工作
  • 我们正在使用共享这些对象的多个线程,并且
  • 我们关心性能和正确性

...那么实际的垃圾回收可能是一个更好的选择。尽管GC在性能方面声誉不佳,但这是相对的。我认为它与锁定智能指针相比非常有利。这就是为什么CLR团队选择真正的GC而不是使用引用计数的原因的重要组成部分。请参阅本文,特别是如果继续进行计数,则对参考分配的含义进行了鲜明的比较:

没有引用计数:

a = b;

引用计数:

if (a != null)
    if (InterlockedDecrement(ref a.m_ref) == 0)
            a.FinalRelease();

if (b != null)
    InterlockedIncrement(ref b.m_ref);

a = b;