【问题标题】:C++: Multithreading and refcounted objectC++:多线程和引用对象
【发布时间】:2010-09-15 00:47:25
【问题描述】:

我目前正在尝试将单线程程序传递给多线程。该软件大量使用“refCounted”对象,这导致了多线程中的一些问题。我正在寻找一些设计模式或可能解决我的问题的东西。

主要问题是线程之间的对象删除,通常删除只是减少引用计数,当refcount等于0时,对象被删除。这在单线程程序中工作得很好,并且可以通过复制大对象来极大地提高性能。

但是,在多线程中,两个线程可能希望同时删除同一个对象,因为该对象受互斥体保护,只有一个线程删除该对象并阻止另一个。但是当它释放互斥体时,另一个线程继续执行无效(释放的对象),这会导致内存损坏。

这是一个使用此类 RefCountedObject 的示例

class RefCountedObject
{
public:
RefCountedObject()
:   _refCount( new U32(1) )
{}

RefCountedObject( const RefCountedObject& obj )
:   _refCount( obj._refCount )
{
    ACE_Guard< ACE_Mutex > guard( _refCountMutex );
    ++(*_refCount);
}

~RefCountedObject()
{
    Destroy();
}

RefCountedObject& operator=( const RefCountedObject& obj )
{
    if( this != &obj )
    {
        Destroy();
        ACE_Guard< ACE_Mutex > guard( _refCountMutex );
        _refCount = obj._refCount;
        ++(*_refCount);
    }

    return *this;
}

private:
    void Destroy()
    {
        ACE_Guard< ACE_Mutex > guard( _refCountMutex );  // thread2 are waiting here
        --(*_refCount);         // This cause a free memory write by the thread2
        if( 0 == *_refCount )
            delete _refCount;
    }

private:
    mutable U32* _refCount;
    mutable ACE_Mutex _refCountMutex; // BAD: this mutex only protect the refCount pointer, not the refCount itself
};

假设两个线程要删除同一个RefCountedObject,都在~RefCountedObject中并调用Destroy(),第一个线程已锁定互斥体,另一个正在等待。在第一个线程删除对象后,第二个线程将继续执行并导致空闲内存写入。

有人遇到过类似问题并找到解决方案吗?


感谢大家的帮助,我意识到我的错误: 互斥体只是保护 refCount 指针,而不是 refCount 本身!我创建了一个受互斥保护的 RefCount 类。互斥体现在在所有 refCounted 对象之间共享。

现在一切正常。

【问题讨论】:

    标签: c++ multithreading refcounting


    【解决方案1】:

    这不是答案,只是一些建议。在这种情况下,在你开始修复任何东西之前,请确保你可以可靠地复制这些问题。有时这很简单,就像在循环中运行你的单元测试一段时间。有时在你的程序中加入一些聪明的睡眠来强制竞争条件是有帮助的。

    引用计数问题往往会持续存在,因此从长远来看,对测试工具的投资将获得回报。

    【讨论】:

    • +1 解决多线程问题的唯一真正方法是使它们可重复。
    【解决方案2】:

    稍微考虑一下您的问题...您的意思是您有 1 个对象(如果 refcount 为 1),但 2 个线程都在其上调用 delete()。我认为这才是你真正的问题所在。

    解决此问题的另一种方法是,如果您想要一个可以在线程之间安全重用的线程对象,则在释放内部内存之前检查引用计数是否大于 1。目前你释放它,然后检查 refcount 是否为 0。

    【讨论】:

      【解决方案3】:

      如果计数是对象的一部分,那么如果一个线程可以尝试增加引用计数,而另一个线程尝试删除 last,则存在固有问题参考。每个指向对象的全局可访问指针的引用计数都需要一个额外的值,因此如果你有一个指针,你总是可以安全地增加引用计数。

      一种选择是使用boost::shared_ptr(see the docs)。您可以使用免费函数atomic_load、atomic_store、atomic_exchange 和atomic_compare_exchange(文档中明显没有这些函数)来确保在访问指向共享对象的全局指针时得到适当的保护。一旦你的线程有一个 shared_ptr 引用一个特定的对象,你就可以使用普通的非原子函数来访问它。

      另一种选择是使用 Joe Seigh 的 atomic_ptr_plus project 中的原子引用计数指针

      【讨论】:

        【解决方案4】:

        我相信沿着这条线的东西会解决你的问题:

        private:
            void Destroy()
            {
        ACE_Guard< ACE_Mutex > guard( _refCountMutex ); // thread2 are waiting here if (_refCount != 0) { --(*_refCount); // This cause a free memory write by the thread2 if( 0 == *_refCount ) { delete _refCount; _refcount = 0; } } } private: mutable U32* _refCount; mutable ACE_Mutex _refCountMutex;

        【讨论】:

          【解决方案5】:

          一种解决方案是使引用计数器成为原子值,这样每次对 destroy 的并发调用都可以安全地进行,实际上只发生 1 次删除,另一种只是减少原子引用计数。

          英特尔线程构建模块库 (TBB) 提供原子值。

          ACE_Atomic_Op 模板中的 ACE 库也是如此。

          Boost 库提供了一个引用计数智能指针库,已经实现了这一点。

          http://www.dre.vanderbilt.edu/Doxygen/Current/html/ace/a00029.html http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm

          【讨论】:

            【解决方案6】:

            您在线程之间共享的任何对象都应使用互斥锁进行保护,这同样适用于 refcount handles !这意味着您永远不会从两个线程中删除对象的最后一个句柄。您可能会同时删除恰好指向一个对象的两个不同句柄。

            在 Windows 中,您可以使用 InterlockedDecrement。这确保了两个减量中的一个将返回 0。只有该线程会删除引用的对象。

            任何其他线程也不能复制两个句柄之一。根据常见的 MT 规则,一个线程可能不会删除另一个线程仍在使用的对象,这也扩展到 refcount 句柄。

            【讨论】:

              【解决方案7】:

              当然,每个线程只需要正确管理引用计数...也就是说,如果 ThreadA 和 ThreadB 都在使用 Obj1,那么 ThreadA 和 ThreadB 都应该拥有对该对象的引用,并且两者都应该在它们被调用时调用 release对象完成。

              在单线程应用程序中,您可能会创建一个引用计数对象,然后您对该对象进行处理并最终调用 release.在多线程程序中,您将创建对象,然后将其传递给您的线程(无论如何您都这样做)。在将对象传递给线程之前,您应该在对象上调用 AddRef() 以赋予线程自己的引用计数。分配对象的线程然后可以调用释放,因为它已经完成了对象。使用该对象的线程将在完成后调用 release,当最后一个引用被释放时,该对象将被清理。

              请注意,您不希望在线程本身上运行的代码在对象上调用 AddRef(),因为在您已调度获取的线程之前,在对象上创建线程调用 release 之间存在竞争条件有机会运行并调用 AddRef()。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2014-12-27
                • 2017-06-28
                • 1970-01-01
                • 2014-10-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多