【问题标题】:boost::shared_ptr drop-in replacementboost::shared_ptr 替换
【发布时间】:2013-02-27 19:24:20
【问题描述】:

最近我开始处理一个遗留项目并尝试修复段错误(双重删除)。其中许多发生在boost::shared_ptr 析构函数或operator=(在包含shared_ptr 的对象上)。该代码包含大量使用 shared_ptr-s,包括复制、reset()-ing、分配等。根据boost docs,我们没有有效的用法 - 在许多线程中破坏/复制/重置相同的 shared_ptr 是不安全的。

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

根据gcc docs,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 exit\n";
        return 0;
}

【问题讨论】:

  • boost::shared_ptr 非常可靠。我认为用另一种实现替换它不会“解决”任何问题。如果您看到双重删除,那么您将继续看到这些,直到您找到并修复您的错误。
  • “每次都似乎不可能锁定”表示您遇到线程问题。更改智能指针实现不会给您带来任何好处。 boost::shared_ptr 没有问题。
  • 您在寻找线程安全的共享指针吗?
  • 我不是说它有问题。我们的代码有问题,它以在 boost 文档中标记为“未定义行为”的方式使用 shared_ptr。 (用链接更新了答案)
  • 我怀疑更改特定的 shared_ptr 会修复您的代码。

标签: c++ boost c++11


【解决方案1】:

第 1 步:像这样构建一个类,并用它替换 boost::shared_ptr&lt;T&gt; 的用法。

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&lt;T&gt; 处理多个线程同时访问同一个变量的难度。

现在,这非常混乱。如果一个线程resets 给定shared_ptr,而另一个线程从它读取,则在读取线程访问它时,从读取的指针可能完全无效。实际上,您需要保护 all 对互斥体中底层指针的访问,这是完全不切实际的。

另一方面,如果您拥有多个阅读器,而不是一个阅读器和一个作者,那么您的状态会更好——理论上,您可以通过在引用计数代码上使用适当的锁来解决问题.

但是,您实际描述的似乎涉及读取和写入同一变量的多个线程。这从根本上被破坏了,仅仅shared_ptr 变量上的线程安全是无法解决的。

【讨论】:

  • 感谢您的回复。可能我应该切换到自定义类。我的实验显示所有 3 个标准实现都失败了这个用例(
  • @PSIAlt:你没读过他的最后一段吗?您无法通过创建新的指针类型来解决问题。除非您要对每个指针实例进行互斥体包装,即使那样,您仍然会遇到问题,因为有人会假设旧指针就在身边。你无法逃避这样一个事实,即你收到了一个漏洞百出的代码库,你需要修复它。
  • @NicolBolas 呃,似乎真的没有别的办法了.. 不知道还有什么.. 看起来我没有机会在不破坏某些东西的情况下重写它。但是,感谢您指出锁定
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    std::shared_ptr 可以使用原子整数,如果编译器/架构/实现支持/使用它。但我不会打赌,这样做会降低你的代码的可移植性。但它可能是一种临时解决方法(例如,有一个正在运行的程序,以便您了解代码应该做什么)。

    编写您自己的 shared_ptr 包装器可能是一种选择,但您的代码仍需要进行死锁审核。

    【讨论】:

    • std::shared_ptr 必须是线程安全的...但 boost::shared_ptr 一样是线程安全的。也就是说,两个不同的 shared_ptr 对象可以引用相同的数据,删除就可以了。但是您不能从两个不同的线程中戳 same shared_ptr 对象。后者是 OP 的问题。
    • shared_ptr 要求是线程安全的,但仅用于并发复制实例,不能同时修改它们
    猜你喜欢
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-04
    • 1970-01-01
    相关资源
    最近更新 更多