C ++: multithreading and refcounted object

I am currently trying to stream a multi-threaded program with a single thread. This software makes heavy use of refCounted objects, which lead to some problems in multithreaded mode. I am looking for some kind of design template or something that can solve my problem.

The main problem is deleting the object between the threads, usually deleting only decreases the reference count, and when refcount is zero, the object is deleted. This works well in a monochrome program and allows you to achieve large performance improvements with a copy of a large object.

However, in multithreaded mode, two threads may want to delete the same object at the same time, since the object is protected by the mutex, only one thread deletes the object and blocks the other. But when it frees the mutex, another thread continues execution with an invalid (freed object), which leads to memory corruption.

Here is an example with this RefCountedObject class

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 blocked the mutex and the other is waiting. After deleting the object on the first thread, the second will continue to execute and will call up free memory.

Does anyone have experience with a similar problem and find a solution?


Thank you all for your help, I understand my mistake: Mutex only protects the refCount pointer, not the refCount! I created the RefCount class, which is protected by a mutex. Mutex is now shared among all refCounted objects.

Now everything is working fine.

+6
c ++ multithreading design
source share
7 answers

If the counter is part of the object, then you have an inherent problem, if one thread may try to increase the reference count while the other tries to delete the last link. For each globally accessible pointer to an object, there should be an additional value for the number of links, so you can always safely increase the reference count if you have a pointer.

One option is to use boost::shared_ptr (see docs) . You can use the free functions atomic_load , atomic_store , atomic_exchange and atomic_compare_exchange (which are not explicitly in the docs) to provide appropriate protection when accessing global pointers for shared objects. When your thread has shared_ptr , referring to a specific object, you can use ordinary non-atomic functions to access it.

Another option is to use the Joe Seigh atomic reflex pointer from your atom_ptr_plus project

+4
source share

Of course, each thread just needs to properly manage reference samples. That is, if ThreadA and ThreadB work with Obj1, then BOTH ThreadA and ThreadB should have a reference to the object, and BOTH should trigger the release when they are done with the object.

In a single-threaded application, it is likely that you have a point at which a reference counting object is created, then do the work on the object and eventually call the release. In a multi-threaded program, you have to create an object and then pass it to your threads (how you do it). Before passing an object to a stream, you must call AddRef () on your object to give the stream its own reference count. Then the thread that allocated the object can cause a release, as was done with the object. The threads that work with the object then cause a release when they are executed, and when the last request is sent, the object will be cleared.

Please note that you do not want the code that was running on the threads themselves to call the AddRef () object, since you then have a race condition between creating a thread that triggers a release on the object, before the threads you send to be able to run and call AddRef ().

+3
source share

Thinking a little about your problem ... you say that you have 1 object (if refcount is 1), and yet 2 threads both call delete () on it. I think this is where your problem really lies.

Otherwise, if you want a stream object that you can safely reuse between threads, check that the refcount is greater than 1 before freeing up the internal memory. You are currently freeing it and then checking to see if the value matches 0.

+1
source share

This is not an answer, but only a few tips. In such a situation, before you start fixing something, make sure that you can reliably duplicate these problems. Sometimes it’s quite simple, because from time to time testing of your device in a cycle is performed. Sometimes, using some smart berths in your program to provide race conditions is helpful.

Countdown problems tend to linger, so investing in test posting will pay off in the long run.

+1
source share

Any object that you use between threads must be protected by a mutex, and the same applies to refcount descriptors! This means that you never delete the last one object descriptor from two threads. Perhaps you are simultaneously deleting two different descriptors that point to the same object.

On Windows, you can use InterlockedDecrement. This ensures that exactly one of the two decrements returns 0. Only this thread will remove the refcounted object.

Any other thread cannot copy one of the two handles. According to general MT rules, one thread cannot delete an object that is still in use by another thread, and this also applies to refcount handle.

0
source share

One solution is to make the reference counter an atomic value, so that each simultaneous call for destruction can safely carry out only 1 deletion actually occurring, and the other simply reduces the number of atom counts.

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

In addition, the ACE library in the ACE_Atomic_Op template.

The Boost library provides a library of smart reference counting pointers 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

0
source share

I believe something along this line will 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;
code>
0
source share

All Articles