I figured it _could_ be useful to have my mutexes created automatically for me on a per object basis. QObjectLocker maintains a pool of mutexes assigned to specified addresses (object pointers) in memory. Otherwise it behaves similarly to QMutexLocker. This way I don’t have to extend or pair up existing objects with their respective mutexes, it happens transparently behind the scenes with (hopefully) little overhead.
Tests have shown that in preferred usage scenario, i.e. when QObjectLocker instance is reused via its unlock()/relock() methods rather than being created and destroyed repetitively, it performs as well as QMutexLocker. In the worst case scenario, when the only usage was via creation/destruction, performance degraded significantly – from 1.375s to 4.867s in a test running 30 dummy threads that just perform 100000 repetitions of lock and unlock on some resources.
All in all – not bad (in my opinion, that is). What do _you_ think? Do I have a point or is this just old man’s rambling? 😉
class QObjectLocker { public: QObjectLocker(void *ptr); ~QObjectLocker(); void unlock(); void relock(); };
qobjectlocker.h
#ifndef QOBJECTLOCKER_H #define QOBJECTLOCKER_H #include <QMap> #include <QMutex> class QObjectLocker { enum { GarbageCollectTimeout = 10000 }; struct Mutex { QMutex *m_Mutex; int m_RefCnt; Mutex() { } Mutex(QMutex *mutex, int refCnt): m_Mutex(mutex), m_RefCnt(refCnt) { } }; typedef QMap<void*, Mutex> Map; typedef QMutableMapIterator<void*, Mutex> MutableMapIterator; static Map m_MutexMap; static QMutex m_MutexMapLock; static int m_Counter; public: QObjectLocker(void *ptr); ~QObjectLocker(); void unlock(); void relock(); private: static void gc(); private: QMutex *m_Mutex; bool m_Locked; void *m_Ptr; }; #include <QThread> #include <QVector> class TestThread: public QThread { Q_OBJECT public: TestThread(const QVector<void*> &resources); protected: void run(); private: QVector<void*> m_Resources; }; #endif // QOBJECTLOCKER_H
qobjectlocker.cpp
#include "qobjectlocker.h" #include <QMutexLocker> #include <QMutableMapIterator> #include <QDebug> QObjectLocker::Map QObjectLocker::m_MutexMap; QMutex QObjectLocker::m_MutexMapLock; int QObjectLocker::m_Counter; QObjectLocker::QObjectLocker(void* ptr): m_Locked(false), m_Ptr(ptr) { QMutexLocker lock(&m_MutexMapLock); if (++m_Counter == GarbageCollectTimeout) { gc(); m_Counter = 0; } Map::iterator it = m_MutexMap.find(ptr); if (it != m_MutexMap.end()) { it->m_RefCnt++; m_Mutex = it->m_Mutex; m_Mutex->lock(); m_Locked = true; return; } m_Mutex = new QMutex(); m_MutexMap[ptr] = Mutex(m_Mutex, 1); m_Mutex->lock(); m_Locked = true; } QObjectLocker::~QObjectLocker() { if (m_Locked) { m_Mutex->unlock(); } QMutexLocker lock(&m_MutexMapLock); m_MutexMap[m_Ptr].m_RefCnt--; } void QObjectLocker::unlock() { if (m_Locked) { m_Mutex->unlock(); m_Locked = false; } } void QObjectLocker::relock() { if (!m_Locked) { m_Mutex->lock(); m_Locked = true; } } void QObjectLocker::gc() { qDebug() << "gc() called"; for (MutableMapIterator it(m_MutexMap); it.hasNext();) { Mutex& m(it.next().value()); if (m.m_RefCnt == 0) { qDebug() << "Removing mutex" << m.m_Mutex << "with reference count" << m.m_RefCnt; it.remove(); } } } // // Testing // TestThread::TestThread(const QVector<void*> &resources): m_Resources(resources) { } void TestThread::run() { for (int i = 0; i < 100000; ++i) { int r = qrand() % m_Resources.size(); QObjectLocker lock(m_Resources[r]); lock.relock(); qDebug() << "Thread" << QThread::currentThreadId() << " got lock on resource" << r; lock.unlock(); qDebug() << "Thread" << QThread::currentThreadId() << " released lock on resource" << r; } } int main() { qsrand(10101); QVector<int> testA; QVector<int> testB; QVector<int> testC; QVector<void*> resources; resources.push_back(&testA); resources.push_back(&testB); resources.push_back(&testC); qDebug() << resources.size(); QThread *thr; for (int i = 0; i < 2; i++) { thr = new TestThread(resources); thr->start(); } thr->wait(); }