Boost::shared_ptr插入式替换

boost::shared_ptr drop-in replacement

本文关键字:替换 插入 shared Boost ptr      更新时间:2023-10-16

最近我开始在一个遗留项目上工作,并试图修复分段错误(双重删除)。其中许多都发生在boost::shared_ptr析构函数或operator=上(在包含shared_ptr的对象上)。代码包含大量使用shared_ptr-s,包括复制、reset()-ing、赋值等。根据boost文档,我们没有有效的使用-在多个线程中销毁/复制/重置同一个shared_ptr是不安全的。

每次锁定似乎是不可能的,所以我正在寻找boost::shared_ptr的插入式替代品。所以问题是:如果我用std::shared_ptrstd::tr1::shared_ptr代替所有的boost::shared_ptr,会解决这个问题吗?似乎tr1是更安全的版本,但我不清楚。第二个问题- c++0x版本比tr1更好吗?(注意我们有GCC 4.4.6,不能升级)

根据gcc文档,c++11 std::shared_ptr应该修复这个问题,但我不确定gcc4.4版本…

UPD:刚刚做了一个实验,现在我知道所有3个实现都在这个代码(gcc 4.4)上做段错误。似乎我应该自定义类或其他解决方案…

#include <iostream>
#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<int> ptrtype;
ptrtype p(new int);
void test() {
        for(long i=0; i<1000000; ++i) {
                ptrtype p1 = p;
                p = ptrtype();
                p.reset( new int );
        }
}
int main() {
        boost::thread_group tg;
        for(int i=0; i<100; ++i) tg.add_thread( new boost::thread(test) );
        tg.join_all();
        std::cout << "Normal exitn";
        return 0;
}

步骤1:像这样构建一个类,并用它替换boost::shared_ptr<T>的使用。

template<typename T>
struct trivial_ptr {
  T* t;
  template<typename U>
  void reset( U* p ) {t=p;}
  void reset( T* p = NULL ) { t=p; }
  template<typename U>
  trivial_ptr<T>& operator=(trivial_shared_ptr<U>const& o) {
    t = o.t;
    return *t;
  }
  explicit trivial_ptr( T* p ):t(p) {}
  ...
};

这个类不是用来运行的,而只是用正确的接口进行编译。编译完成后,您可以确保知道正在使用的是boost::shared_ptr接口的哪些部分。(您是否使用自定义删除程序?等等——问题可能更困难,也可能更容易,上面的代码可以帮助测试它)

一旦你到了那里,你就可以知道编写一个shared_ptr<T>处理多个线程同时访问同一个变量是多么困难。

这是非常混乱的。如果一个线程reset是给定的shared_ptr,而另一个线程从它读取,那么当读取线程访问它时,从读取的指针可能完全无效。实际上,您需要保护所有对互斥锁中底层指针的访问,这是完全不切实际的。

另一方面,如果您有多个读取器,而不是一个读取器和一个写入器,则情况会更好——理论上,您可以通过在引用计数代码上使用适当的锁来解决这个问题。

然而,你实际描述的似乎涉及多个线程对同一个变量的读写。这在某种程度上是根本的,仅仅是shared_ptr变量上的线程安全是无法修复的。

您似乎遇到的问题是试图在两个不同的线程中修改变量的相同实例(又称数据竞争)。Shared_ptr并不比int型更受保护。您对gcc文档的参考说明了同样的事情("与内置类型相同级别的线程安全")。试图在两个不同的线程中修改同一个shared_ptr实例需要某种同步来防止数据争用。尝试修改指向同一对象的shared_ptr的两个不同实例是可以的(没有数据竞争,或者shared_ptr必须在内部实现防止数据竞争所必需的任何东西)。试图修改它们所指向的对象也是一种数据竞争。

std::shared_ptr 可以使用原子整型,如果编译器/体系结构/实现支持的话。但我不会这么做,因为这样做会降低代码的可移植性。但它可以作为一个临时的解决方案(例如,有一个正在运行的程序,这样你就可以理解代码应该做什么)。

编写自己的shared_ptr包装器可能是一个选项,但是您的代码仍然需要审计死锁。