读写线程安全智能指针在c++, x86-64
Read-write thread-safe smart pointer in C++, x86-64
我开发了一些无锁数据结构,出现了以下问题:
我有一个writer线程,它在堆上创建对象,并将它们包装在带有引用计数器的智能指针中。我也有很多reader线程,用来处理这些对象。代码可以像这样:
SmartPtr ptr;
class Reader : public Thread {
virtual void Run {
for (;;) {
SmartPtr local(ptr);
// do smth
}
}
};
class Writer : public Thread {
virtual void Run {
for (;;) {
SmartPtr newPtr(new Object);
ptr = newPtr;
}
}
};
int main() {
Pool* pool = SystemThreadPool();
pool->Run(new Reader());
pool->Run(new Writer());
for (;;) // wait for crash :(
}
当我创建ptr
的线程本地副本时它至少意味着
- 读取地址
- 增量引用计数器。
我不能自动地完成这两个操作,因此有时我的读者会处理已删除的对象。
问题是-我应该使用什么样的智能指针,使读写访问从几个线程与正确的内存管理成为可能?应该存在解决方案,因为Java程序员甚至不关心这样的问题,只是简单地依赖于所有对象都是引用,并且只有在没有人使用它们时才会删除它们。
对于PowerPC,我找到http://drdobbs.com/184401888,看起来不错,但使用Load-Linked和Store-Conditional指令,我们在x86中没有。
据我所知,boost指针只使用锁提供这样的功能。
boost::shared_ptr有atomic_store,它使用"无锁"自旋锁,在99%的可能情况下应该足够快。
boost::shared_ptr<Object> ptr;
class Reader : public Thread {
virtual void Run {
for (;;) {
boost::shared_ptr<Object> local(boost::atomic_load(&ptr));
// do smth
}
}
};
class Writer : public Thread {
virtual void Run {
for (;;) {
boost::shared_ptr<Object> newPtr(new Object);
boost::atomic_store(&ptr, newPtr);
}
}
};
int main() {
Pool* pool = SystemThreadPool();
pool->Run(new Reader());
pool->Run(new Writer());
for (;;)
}
编辑:作为对下面评论的回应,实现在"boost/shared_ptr.hpp"…
template<class T> void atomic_store( shared_ptr<T> * p, shared_ptr<T> r )
{
boost::detail::spinlock_pool<2>::scoped_lock lock( p );
p->swap( r );
}
template<class T> shared_ptr<T> atomic_exchange( shared_ptr<T> * p, shared_ptr<T> r )
{
boost::detail::spinlock & sp = boost::detail::spinlock_pool<2>::spinlock_for( p );
sp.lock();
p->swap( r );
sp.unlock();
return r; // return std::move( r )
}
使用一些工具,您应该能够使用InterlockedCompareExchange128完成此操作。将引用计数和指针存储在包含2个元素的__int64数组中。如果引用计数在数组[0]中,指针在数组[1]中,原子更新将如下所示:
while(true)
{
__int64 comparand[2];
comparand[0] = refCount;
comparand[1] = pointer;
if(1 == InterlockedCompareExchange128(
array,
pointer,
refCount + 1,
comparand))
{
// Pointer is ready for use. Exit the while loop.
}
}
如果InterlockedCompareExchange128内部函数不可用于您的编译器,那么您可以使用底层CMPXCHG16B指令代替,如果您不介意在汇编语言中摆弄。
RobH提出的解决方案不起作用。它与最初的问题有相同的问题:当访问引用count对象时,它可能已经被删除了。
在没有全局锁(如boost::atomic_store)或条件读/写指令的情况下,我认为解决这个问题的唯一方法是以某种方式延迟对象的销毁(或共享引用计数对象,如果使用这样的东西)。所以zennehoy有一个好主意,但是他的方法太不安全了。
我可以这样做:在写线程中保存所有指针的副本,这样写线程就可以控制对象的销毁:
class Writer : public Thread {
virtual void Run() {
list<SmartPtr> ptrs; //list that holds all the old ptr values
for (;;) {
SmartPtr newPtr(new Object);
if(ptr)
ptrs.push_back(ptr); //push previous pointer into the list
ptr = newPtr;
//Periodically go through the list and destroy objects that are not
//referenced by other threads
for(auto it=ptrs.begin(); it!=ptrs.end(); )
if(it->refCount()==1)
it = ptrs.erase(it);
else
++it;
}
}
};
然而,对智能指针类仍然有要求。这对shared_ptr不起作用,因为读写不是原子的。它几乎可以与boost::intrusive_ptr一起工作。intrusive_ptr上的赋值是这样实现的(伪代码):
//create temporary from rhs
tmp.ptr = rhs.ptr;
if(tmp.ptr)
intrusive_ptr_add_ref(tmp.ptr);
//swap(tmp,lhs)
T* x = lhs.ptr;
lhs.ptr = tmp.ptr;
tmp.ptr = x;
//destroy temporary
if(tmp.ptr)
intrusive_ptr_release(tmp.ptr);
据我所知,这里唯一缺少的是lhs.ptr = tmp.ptr;
之前的编译器级内存栅栏。加上这一点,在严格的条件下,读取rhs
和写入lhs
都是线程安全的:1)x86或x64架构2)原子引用计数3)rhs
refcount在赋值期间不得变为零(由上面的Writer代码保证)4)只有一个线程写入lhs
(使用CAS您可以有几个Writer)。
这在java中更容易工作的原因是垃圾收集。在c++中,当你想要删除一个值时,你必须手动确保它不会刚开始被另一个线程使用。
我在类似情况下使用的解决方案是简单地延迟删除值。我创建了一个单独的线程,它遍历要删除的内容列表。当我想要删除某些内容时,我将其添加到带有时间戳的列表中。在实际删除该值之前,删除线程将等待该时间戳之后的某个固定时间。您只需要确保延迟足够大,以保证对该值的任何临时使用都已完成。
100毫秒对我来说已经足够了,为了安全起见,我选择了几秒钟。
- 您选择的 CPU 不支持 x86-64 指令集
- std::atomic::fetch_add是x86-64上的串行化操作
- 谷歌Play游戏服务和64位x86支持
- Apple Mach-O-Linker 错误:在体系结构 x86-64 中找不到从(行)引用的变量
- x86-64 程序集:为什么偏移 25 字节
- DIS(P X)-X在GCC Linux X86-64 C 中始终导致Pointer P和Integer X的P
- 什么是 gcc Linux x86-64 C++中的有效指针
- X86-64上的C :何时通过结构/类在寄存器中返回
- x86-64 调用约定中的返回值
- C++Mac上使用Linux库(x86_64-Apple-Darwin上的elf64-x86-64)
- x86-64上检查指针范围是否跨越N字节对齐地址的最快方法
- 如何使用C++11为x86(-64)安全地编写测试和测试集(TATAS)自旋锁
- 在 x86-64 平台上为 C(++) 中的 64 位无符号参数计算 (a*b)%n FAST
- x86-64 movl and cmpl difference
- visual x86-64 MSVC++/Intel C++更改int、long等的大小
- x86-64 汇编程序中的无限循环
- Large PCIe DMA Linux x86-64
- 读写线程安全智能指针在c++, x86-64
- (clang / llvm-mc / lld) hello world (x86-64 Windows & Linux)
- 用于 x86/64 的 Itanium 交叉编译器