mirror of
https://github.com/LMMS/lmms.git
synced 2026-05-06 05:45:51 -04:00
Use atomics to count shared_object without locks
C++11 (and subsequent C++ standards) provide portable ways to issue atomic hardware instructions, which allow multiple threads to load, store, and modify integers without taking a lock. The standard also defines a memory model that lets you express the ordering guarantees around these atomic operations. (x86 is relatively strongly-ordered, but many other common architectures, such as ARM, are free to reorder loads and stores unless told not to.) This patch removes the lock from shared_object and replaces it with the standard thread-safe reference counting implementation used in C++'s std::shared_ptr, Rust's std::sync::Arc, and many others. Additional resources on the topic: https://assets.bitbashing.io/papers/concurrency-primer.pdf https://www.youtube.com/watch?v=ZQFzMfHIxng
This commit is contained in:
@@ -26,15 +26,13 @@
|
||||
#ifndef SHARED_OBJECT_H
|
||||
#define SHARED_OBJECT_H
|
||||
|
||||
#include <QtCore/QMutex>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
class sharedObject
|
||||
{
|
||||
public:
|
||||
sharedObject() :
|
||||
m_referenceCount( 1 ),
|
||||
m_lock()
|
||||
m_referenceCount(1)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -45,19 +43,34 @@ public:
|
||||
template<class T>
|
||||
static T* ref( T* object )
|
||||
{
|
||||
object->m_lock.lock();
|
||||
// TODO: Use QShared
|
||||
++object->m_referenceCount;
|
||||
object->m_lock.unlock();
|
||||
// Incrementing an atomic reference count can be relaxed since no action
|
||||
// is ever taken as a result of increasing the count.
|
||||
// Other loads and stores can be reordered around this without consequence.
|
||||
object->m_referenceCount.fetch_add(1, std::memory_order_relaxed);
|
||||
return object;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void unref( T* object )
|
||||
{
|
||||
object->m_lock.lock();
|
||||
bool deleteObject = --object->m_referenceCount <= 0;
|
||||
object->m_lock.unlock();
|
||||
// When decrementing an atomic reference count, we need to provide
|
||||
// two ordering guarantees:
|
||||
// 1. All reads and writes to the referenced object occur before
|
||||
// the count reaches zero.
|
||||
// 2. Deletion occurs after the count reaches zero.
|
||||
//
|
||||
// To accomplish this, each decrement must be store-released,
|
||||
// and the final thread (which is deleting the referenced data)
|
||||
// must load-acquire those stores.
|
||||
// The simplest way to do this to give the decrement acquire-release
|
||||
// semantics.
|
||||
//
|
||||
// See https://www.boost.org/doc/libs/1_67_0/doc/html/atomic/usage_examples.html
|
||||
// for further discussion, along with a slightly more complicated
|
||||
// (but possibly more performant on weakly-ordered hardware like ARM)
|
||||
// approach.
|
||||
const bool deleteObject =
|
||||
object->m_referenceCount.fetch_sub(1, std::memory_order_acq_rel) == 1;
|
||||
|
||||
if ( deleteObject )
|
||||
{
|
||||
@@ -65,20 +78,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// keep clang happy which complaines about unused member variable
|
||||
void dummy()
|
||||
{
|
||||
m_referenceCount = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_referenceCount;
|
||||
QMutex m_lock;
|
||||
|
||||
std::atomic_int m_referenceCount;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user