C++:多线程和引用计数对象

发布于 2024-07-06 19:19:01 字数 1672 浏览 5 评论 0原文

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

主要问题是线程之间的对象删除,通常删除只会减少引用计数,当引用计数等于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 对象之间共享。

现在一切正常。

I'm currently trying to pass a mono threaded program to multithread. This software do heavy usage of "refCounted" objects, which lead to some issues in multithread. I'm looking for some design pattern or something that might solve my problem.

The main problem is object deletion between thread, normally deletion only decrement the reference counting, and when refcount is equal to zero, then the object is deleted. This work well in monothread program, and allow some great performance improvement with copy of big object.

However, in multithread, two threads might want to delete the same object concurrently, as the object is protected by a mutex, only one thread delete the object and block the other one. But when it releases the mutex, then the other thread continue its execution with invalid (freed object), which lead to memory corruption.

Here is an example with this class 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
};

Suppose that two threads want to delete the same RefCountedObject, both are in ~RefCountedObject and call Destroy(), the first thread has locked the mutex and the other one is waiting. After the deletion of the object by the first thread, the second will continue its execution and cause a free memory write.

Anyone has experience with a similar problem and found a solution ?


Thanks all for your help, I realize my mistake:
The mutex is only protecting refCount pointer, not the refCount itself! I've created a RefCount class which is mutex protected. The mutex is now shared between all refCounted object.

Now all works fine.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

如歌彻婉言 2024-07-13 19:19:01

如果计数是对象的一部分,那么如果一个线程可能试图增加引用计数,而另一个线程试图删除最后引用,那么您就会遇到一个固有的问题。 对于每个可全局访问的对象指针,引用计数上需要有一个额外的值,因此,如果您有指针,则始终可以安全地增加引用计数。

一种选择是使用 boost::shared_ptr (请参阅文档)。 您可以使用免费函数atomic_loadatomic_storeatomic_exchangeatomic_compare_exchange(文档中明显缺少这些函数)确保在访问共享对象的全局指针时得到适当的保护。 一旦您的线程获得了引用特定对象的shared_ptr,您就可以使用普通的非原子函数来访问它。

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

If the count is part of the object then you have an inherent problem if one thread can be trying to increase the reference count whilst another is trying to remove the last reference. There needs to be an extra value on the ref count for each globally accessible pointer to the object, so you can always safely increase the ref count if you've got a pointer.

One option would be to use boost::shared_ptr (see the docs). You can use the free functions atomic_load, atomic_store, atomic_exchange and atomic_compare_exchange (which are conspicuously absent from the docs) to ensure suitable protection when accessing global pointers to shared objects. Once your thread has got a shared_ptr referring to a particular object you can use the normal non-atomic functions to access it.

Another option is to use Joe Seigh's atomic ref-counted pointer from his atomic_ptr_plus project

小糖芽 2024-07-13 19:19:01

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

在单线程应用程序中,您可能会创建一个引用计数对象,然后对该对象进行操作并最终调用释放。 在多线程程序中,您将创建该对象,然后将其传递给您的线程(无论您如何执行)。 在将对象传递给线程之前,您应该对对象调用 AddRef() 以便为线程提供自己的引用计数。 然后,分配该对象的线程可以在处理该对象时调用release。 处理该对象的线程在完成后将调用release,并且当释放最后一个引用时,该对象将被清理。

请注意,您不希望在线程本身上运行的代码对对象调用 AddRef(),因为这样在您分派的线程之前,在创建对象上调用 release 的线程之间就会出现竞争条件运行并调用 AddRef()。

Surely each thread simply needs to manage the reference counts correctly... That is, if ThreadA and ThreadB are both working with Obj1 then BOTH ThreadA and ThreadB should own a reference to the object and BOTH should call release when they're done with the object.

In a single threaded application it's likely that you have a point where a reference counted object is created, you then do work on the object and eventually call release. In a multi-threaded program you would create the object and then pass it to your threads (however you do that). Before passing the object to the thread you should call AddRef() on your object to give the thread its own reference count. The thread that allocated the object can then call release as it's done with the object. The threads that are working with the object will then call release when they're done and when the last reference is released the object will be cleaned up.

Note that you dont want the code that's running on the threads themselves to call AddRef() on the object as you then have a race condition between the creating thread calling release on the object before the threads that you've dispatched to get a chance to run and call AddRef().

仙女山的月亮 2024-07-13 19:19:01

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

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

thinking about your issue a little... what you're saying is that you have 1 object (if the refcount is 1) and yet 2 threads both call delete() on it. I think this is where your problem truly lies.

The other way round this issue, if you want a threaded object you can safely reuse between threads, is to check that the refcount is greater than 1 before freeing internal memory. Currently you free it and then check whether the refcount is 0.

十雾 2024-07-13 19:19:01

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

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

This isn't an answer, but just a bit of advice. In a situation like this, before you start fixing anything, please make sure you can reliably duplicate these problems. Sometimes this is a simple as running your unit tests in a loop for a while. Sometimes putting some clever Sleeps into your program to force race conditions is helpful.

Ref counting problems tend to linger, so an investment in your test harness will pay off in the long run.

琴流音 2024-07-13 19:19:01

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

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

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

Any object that you are sharing between threads should be protected with a mutex, and the same applies to refcount handles ! That means you will never be deleting the last one handle to an object from two threads. You might be concurrently deleting two distinct handles that happen to point to one object.

In Windows, you could use InterlockedDecrement. This ensures that precisely one of the two decrements will return 0. Only that thread will delete the refcounted object.

Any other thread cannot have been copying one of the two handles either. By common MT rules one thread may not delete an object still used by another thread, and this extends to refcount handles too.

毅然前行 2024-07-13 19:19:01

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

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

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

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

http://www.dre.vanderbilt.edu/Doxygen/当前/html/ace/a00029.html
http://www.boost.org/doc/libs/发布/libs/smart_ptr/shared_ptr.htm

One solution is to make the reference counter an atomic value, so that each concurrent call to destroy can safely proceed with only 1 deletion actually occurring, the other merely decrementing the atomic reference count.

The Intel Thread Building Blocks library (TBB) provides atomic values.

Also, so does the ACE library in the ACE_Atomic_Op template.

The Boost library provides a reference counting smart pointer library that already implements this.

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

这个俗人 2024-07-13 19:19:01

我相信沿着这条线的事情可以解决您的问题:

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;

I believe something along this line would solve your problem:

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;

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文