【发布时间】:2016-09-14 04:16:28
【问题描述】:
一般来说,最广为人知的 C++ 中引用计数智能 ptr 类的实现,包括标准 std::shared_ptr,使用原子引用计数,但不提供对同一智能 ptr 实例的原子访问。换句话说,多个线程可以安全地对指向同一个共享对象的单独的shared_ptr 实例进行操作,但是多个线程不能安全地读/写同一个shared_ptr 实例的实例而不提供某种类型诸如互斥锁之类的同步。
称为“atomic_shared_ptr”的shared_ptr 的原子版本是proposed,并且初步的implementations 已经存在。据推测,atomic_shared_ptr 可以通过自旋锁或互斥锁轻松实现,但也可以使用无锁实现。
在研究了其中一些实现之后,有一点很明显:实现无锁std::shared_ptr 非常困难,而且似乎需要很多compare_and_exchange 操作,这让我怀疑简单的自旋锁是否会更好性能。
实现无锁引用计数指针如此困难的主要原因是读取共享控制块(或共享对象本身,如果我们'正在谈论一个侵入式共享指针),并修改引用计数。
换句话说,您甚至无法安全地读取引用计数,因为您永远不知道其他线程何时释放了引用计数所在的内存。
因此,通常会采用各种复杂的策略来创建无锁版本。 implementation here 看起来像使用双引用计数策略,其中有“本地”引用来计算并发访问 shared_ptr 实例的线程数,然后是“共享”或“全局”引用来计算数量指向共享对象的 shared_ptr 实例。
鉴于所有这些复杂性,我真的很惊讶地发现 Dobbs 博士的一篇文章,来自 2004 不少于(在 C++11 原子之前),似乎漫不经心地解决了整个问题:
http://www.drdobbs.com/atomic-reference-counting-pointers/184401888
看起来作者声称能够:
"... [读取] 指向计数器的指针,递增计数器,然后 返回指针——所有其他线程都不能 导致错误的结果”
但我真的不明白他实际实现这一点的方式。他正在使用(非便携式)PowerPC 指令(LL/SC 原语lwarx 和stwcx)来实现这一目标。
执行此操作的相关代码是他所谓的“aIandF”(原子增量和获取),他将其定义为:
addr aIandF(addr r1){
addr tmp;int c;
do{
do{
tmp = *r1;
if(!tmp)break;
c = lwarx(tmp);
}while(tmp != *r1);
}while(tmp && !stwcx(tmp,c+1));
return tmp;
};
显然,addr 是一个指针类型,指向拥有引用计数变量的共享对象。
我的问题是:这是否仅适用于支持 LL/SC 操作的架构? cmpxchg 似乎是不可能的。其次,这究竟是如何工作的?我已经读过这段代码几次了,我真的不明白发生了什么。我了解LL/SC 原语的作用,但我无法理解代码。
我能理解的最好的是addr r1是指向共享对象的指针的地址,也是指向引用计数的指针的地址(我猜是指引用计数变量必须是定义共享对象的struct 的第一个成员)。然后他取消引用addr(获取共享对象的实际地址)。然后,他链接加载存储在tmp中的地址的值,并将结果存储在c中。这是计数器值。然后,他有条件地将递增的值(如果 tmp 已更改,则会失败)存储回 tmp。
我不明白这是如何工作的。共享对象的地址可能永远不会改变,LL/SC 可能会成功 - 但是如果另一个线程同时释放了共享对象,这对我们有什么帮助?
【问题讨论】:
-
@Anthony Williams:作为
atomic_shared_ptr的实现之一的作者,也许您可以对此发表评论? -
aIandF似乎等同于内置的 GCC atomic__sync_add_and_fetch/__atomic_add_fetch。 -
@jxh 不过锁了。
标签: c++ c++11 atomic smart-pointers