今天看到为知笔记源代码写log用了QMutex锁,想找下关于QMutex速度的的文章,就找到了这篇。可以从后面的对比,看到速度是每秒至少100万次。这么高的速度,写写log是绰绰有余了。
转载https://woboq.com/blog/qreadwritelock-gets-faster-in-qt57.html
QReadWriteLock gets faster in Qt 5.7
In Qt 5.0 already, QMutex got revamped to be fast. In the non contended case, locking and unlocking is basically only a simple atomic instruction and it does not allocate memory making it really light. QReadWriteLock however did not get the same optimizations. Now in Qt 5.7, it gets on par with QMutex.
QReadWriteLock
QReadWriteLock
‘s purpose is about the same as the one of QMutex
: to protect a critical section. The difference is that QReadWriteLock proposes two different locking modes: read or write. This allows several readers to access the critical section at the same time, and therefore be potentially more efficient than a QMutex. At least that was the intention. But the problem is that, before Qt 5.7, QReadWriteLock’s implementation was not as optimized as QMutex. In fact, QReadWriteLock was internally locking and unlocking a QMutex on every call (read or write). So QReadWriteLock was in fact slower than QMutex, unless the read section was held for a very long time, under contention.
For example, the internals of QMetaType were using a QReadWriteLock for the QMetaType database. This makes sense because that database is accessed very often for reading (every time one creates or operates on a QVariant) and very seldom accessed for writing (only when you need to register a new type the first time it is used). However, the QReadWriteLock locking (for read) was so slow that it took a significant amount of time in some QML application that use lots of QVariants, for example with Qt3D.
It was even proposed to replace QReadWriteLock by a QMutex within QMetaType. This would have saved 40% of the time of the QVariant creation. This was not necessary because I improved QReadWriteLock in Qt 5.7 to make it at least as fast as QMutex.
QMutex
QMutex is itself quite efficient already. I described the internals of QMutex in a previous article. Here is a reminder on the important aspects on QMutex:
sizeof(QMutex) == sizeof(void*)
, without any additional allocation.- The non-contended case is basically only an atomic operation for lock or unlock
- In case we need to block, fallback to pthread or native locking primitives
QReadWriteLock in Qt 5.7
I optimized QReadWriteLock to bring it on par with QMutex, using the the same implementation principle.
QReadWriteLock only has one member: a QAtomicPointer named d_ptr
. Depending on the value of d_ptr
, the lock is in the following state:
- When
d_ptr == 0x0
(all the bits are 0): unlocked and non-recursive. No readers or writers holding nor waiting on it. - When
d_ptr & 0x1
(the least significant bit is set): one or several readers are currently holding the lock. No writers are waiting and the lock is non-recursive. The amount of readers is(d_ptr >> 4) + 1
. - When
d_ptr == 0x2
: we are locked for write and nobody is waiting. - In any other case, when the two least significant bits are 0 but the remaining bits contain data,
d_ptr
points to aQReadWriteLockPrivate
object which either means that the lock is recursive, or that it is currently locked and threads are possibly waiting. The QReadWriteLockPrivate has a condition variable allowing to block or wake threads.
In other words, the two least significant bits encode the state. When it is a pointer to a QReadWriteLock, those two bits will always be 0 since pointers must be properly aligned to 32 or 64 bits addresses for the CPU to use them.
This table recap the state depending on the two least significant bits:
if d_ptr is fully 0, then the lock is unlocked. |
|
00 |
If d_ptr is not fully 0, it is pointer to a QReadWriteLockPrivate . |
01 |
One or several readers are currently holding the lock. The amount of reader is (d_ptr >> 4) + 1 . |
10 |
One writer is holding the lock and nobody is waiting |
We therefore define a few constants to help us read the code.
1 2 3 4 5 6 7 8 9 |
<b>enum</b> { <dfn id="StateMask" class="enum" title="StateMask" data-ref="StateMask">StateMask</dfn> = <var>0x3</var>, <dfn id="StateLockedForRead" class="enum" title="StateLockedForRead" data-ref="StateLockedForRead">StateLockedForRead</dfn> = <var>0x1</var>, <dfn id="StateLockedForWrite" class="enum" title="StateLockedForWrite" data-ref="StateLockedForWrite">StateLockedForWrite</dfn> = <var>0x2</var>, }; <em>const</em> <em>auto</em> <dfn id="_ZL18dummyLockedForRead" class="tu decl def" title="dummyLockedForRead" data-type="QReadWriteLockPrivate *const" data-ref="_ZL18dummyLockedForRead">dummyLockedForRead</dfn> = <b>reinterpret_cast</b><<a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a> *>(<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="enum" title="StateLockedForRead" data-ref="StateLockedForRead">StateLockedForRead</span>)); <em>const</em> <em>auto</em> <dfn id="_ZL19dummyLockedForWrite" class="tu decl def" title="dummyLockedForWrite" data-type="QReadWriteLockPrivate *const" data-ref="_ZL19dummyLockedForWrite">dummyLockedForWrite</dfn> = <b>reinterpret_cast</b><<a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a> *>(<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="enum" title="StateLockedForWrite" data-ref="StateLockedForWrite">StateLockedForWrite</span>)); <b>inline</b> <em>bool</em> <dfn id="_Z19isUncontendedLockedPK21QReadWriteLockPrivate" class="decl def" title="isUncontendedLocked" data-ref="_Z19isUncontendedLockedPK21QReadWriteLockPrivate">isUncontendedLocked</dfn>(<em>const</em> <a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a> *<dfn id="1d" class="local col1 decl" title="d" data-type="const QReadWriteLockPrivate *" data-ref="1d">d</dfn>) { <b>return</b> <a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="local col1 ref" title="d" data-ref="1d">d</span>) & <span class="enum" title="StateMask" data-ref="StateMask">StateMask</span>; } |
Aside: The code assumes that the null pointer value is equal to binary 0, which is not guaranteed by the C++ standard, but holds true on every supported platform.
lockForRead
The really fast case happens when there is no contention. If we can atomically swap from 0 to StateLockedForRead
, we have the lock and there is nothing to do. If there already are readers, we need to increase the reader count, atomically. If a writer already holds the lock, then we need to block. In order to block, we will assign a QReadWriteLockPrivate and wait on its condition variable. We call QReadWriteLockPrivate::allocate()
which will pop an unused QReadWriteLockPrivate from a lock-free stack (or allocates a new one if the stack is empty). Indeed, we can never free any of the QReadWriteLockPrivate as another thread might still hold pointer to it and de-reference it. So when we release a QReadWriteLockPrivate, we put it in a lock-free stack.
lockForRead
actually calls tryLockForRead(-1)
, passing -1 as the timeout means “wait forever until we get the lock”.
Here is the slightly edited code. (original)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<em>bool</em> <a class="type" title="QReadWriteLock" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock" data-ref="QReadWriteLock" data-proj="qtbase">QReadWriteLock</a>::<dfn id="_ZN14QReadWriteLock14tryLockForReadEi" class="decl def" title="QReadWriteLock::tryLockForRead" data-ref="_ZN14QReadWriteLock14tryLockForReadEi">tryLockForRead</dfn>(<em>int</em> <dfn id="2timeout" class="local col2 decl" title="timeout" data-type="int" data-ref="2timeout">timeout</dfn>) { <i>// Fast case: non contended:</i> <a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a> *<dfn id="3d" class="local col3 decl" title="d" data-type="QReadWriteLockPrivate *" data-ref="3d">d</dfn>; <b>if</b> (<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetAcquire" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-proj="qtbase">testAndSetAcquire</a>(<b>nullptr</b>, <span class="tu ref" title="dummyLockedForRead" data-use="r" data-ref="_ZL18dummyLockedForRead">dummyLockedForRead</span>, <span class="refarg"><span class="local col3 ref" title="d" data-ref="3d">d</span></span>)) <b>return</b> <b>true</b>; <b>while</b> (<b>true</b>) { <b>if</b> (<span class="local col3 ref" title="d" data-ref="3d">d</span> == <var>0</var>) { <b>if</b> (!<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetAcquire" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-proj="qtbase">testAndSetAcquire</a>(<b>nullptr</b>, <span class="tu ref" title="dummyLockedForRead" data-use="r" data-ref="_ZL18dummyLockedForRead">dummyLockedForRead</span>, <span class="refarg"><span class="local col3 ref" title="d" data-ref="3d">d</span></span>)) <b>continue</b>; <b>return</b> <b>true</b>; } <b>if</b> ((<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="local col3 ref" title="d" data-ref="3d">d</span>) & <span class="enum" title="StateMask" data-ref="StateMask">StateMask</span>) == <span class="enum" title="StateLockedForRead" data-ref="StateLockedForRead">StateLockedForRead</span>) { <i>// locked for read, increase the counter</i> <em>const</em> <em>auto</em> <dfn id="4val" class="local col4 decl" title="val" data-type="QReadWriteLockPrivate *const" data-ref="4val">val</dfn> = <b>reinterpret_cast</b><<a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a> *>(<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="local col3 ref" title="d" data-ref="3d">d</span>) + (<var>1U</var><<<var>4</var>)); <b>if</b> (!<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetAcquire" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-proj="qtbase">testAndSetAcquire</a>(<span class="local col3 ref" title="d" data-ref="3d">d</span>, <span class="local col4 ref" title="val" data-ref="4val">val</span>, <span class="refarg"><span class="local col3 ref" title="d" data-ref="3d">d</span></span>)) <b>continue</b>; <b>return</b> <b>true</b>; } <b>if</b> (<span class="local col3 ref" title="d" data-ref="3d">d</span> == <span class="tu ref" title="dummyLockedForWrite" data-use="r" data-ref="_ZL19dummyLockedForWrite">dummyLockedForWrite</span>) { <b>if</b> (!<span class="local col2 ref" title="timeout" data-ref="2timeout">timeout</span>) <b>return</b> <b>false</b>; <i>// locked for write, assign a d_ptr and wait.</i> <em>auto</em> <dfn id="5val" class="local col5 decl" title="val" data-type="QReadWriteLockPrivate *" data-ref="5val">val</dfn> = <a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a>::<a class="ref" title="QReadWriteLockPrivate::allocate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate8allocateEv" data-ref="_ZN21QReadWriteLockPrivate8allocateEv" data-proj="qtbase">allocate</a>(); <span class="local col5 ref" title="val" data-ref="5val">val</span>-><a class="ref" title="QReadWriteLockPrivate::writerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::writerCount" data-ref="QReadWriteLockPrivate::writerCount" data-proj="qtbase">writerCount</a> = <var>1</var>; <b>if</b> (!<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetOrdered" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetOrderedEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetOrderedEPT_S1_RS1_" data-proj="qtbase">testAndSetOrdered</a>(<span class="local col3 ref" title="d" data-ref="3d">d</span>, <span class="local col5 ref" title="val" data-ref="5val">val</span>, <span class="refarg"><span class="local col3 ref" title="d" data-ref="3d">d</span></span>)) { <span class="local col5 ref" title="val" data-ref="5val">val</span>-><a class="ref" title="QReadWriteLockPrivate::writerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::writerCount" data-ref="QReadWriteLockPrivate::writerCount" data-proj="qtbase">writerCount</a> = <var>0</var>; <span class="local col5 ref" title="val" data-ref="5val">val</span>-><a class="ref" title="QReadWriteLockPrivate::release" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate7releaseEv" data-ref="_ZN21QReadWriteLockPrivate7releaseEv" data-proj="qtbase">release</a>(); <b>continue</b>; } <span class="local col3 ref" title="d" data-ref="3d">d</span> = <span class="local col5 ref" title="val" data-ref="5val">val</span>; } <a class="macro" title="((!(!isUncontendedLocked(d))) ? qt_assert("!isUncontendedLocked(d)","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",55) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#714" data-ref="_M/Q_ASSERT" data-proj="qtbase">Q_ASSERT</a>(!<span class="ref" title="isUncontendedLocked" data-ref="_Z19isUncontendedLockedPK21QReadWriteLockPrivate">isUncontendedLocked</span>(<span class="local col3 ref" title="d" data-ref="3d">d</span>)); <i>// d is an actual pointer;</i> <b>if</b> (<span class="local col3 ref" title="d" data-ref="3d">d</span>-><a class="ref" title="QReadWriteLockPrivate::recursive" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::recursive" data-ref="QReadWriteLockPrivate::recursive" data-proj="qtbase">recursive</a>) <b>return</b> <span class="local col3 ref" title="d" data-ref="3d">d</span>-><a class="ref" title="QReadWriteLockPrivate::recursiveLockForRead" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate20recursiveLockForReadEi" data-ref="_ZN21QReadWriteLockPrivate20recursiveLockForReadEi" data-proj="qtbase">recursiveLockForRead</a>(<span class="local col2 ref" title="timeout" data-ref="2timeout">timeout</span>); <a class="type" title="QMutexLocker" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qmutex.h.html#QMutexLocker" data-ref="QMutexLocker" data-proj="qtbase">QMutexLocker</a> <dfn id="6lock" class="local col6 decl" title="lock" data-type="QMutexLocker" data-ref="6lock">lock</dfn><a class="ref" title="QMutexLocker::QMutexLocker" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qmutex.h.html#_ZN12QMutexLockerC1EP11QBasicMutex" data-ref="_ZN12QMutexLockerC1EP11QBasicMutex" data-proj="qtbase">(</a>&<span class="local col3 ref" title="d" data-ref="3d">d</span>-><a class="ref" title="QReadWriteLockPrivate::mutex" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::mutex" data-ref="QReadWriteLockPrivate::mutex" data-proj="qtbase">mutex</a>); <b>if</b> (<span class="local col3 ref" title="d" data-ref="3d">d</span> != <a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::load" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZNK19QBasicAtomicPointer4loadEv" data-ref="_ZNK19QBasicAtomicPointer4loadEv" data-proj="qtbase">load</a>()) { <i>// d_ptr has changed: this QReadWriteLock was unlocked before we had</i> <i> // time to lock d->mutex.</i> <i> // We are holding a lock to a mutex within a QReadWriteLockPrivate</i> <i> // that is already released (or even is already re-used). That's ok</i> <i> // because the QFreeList never frees them.</i> <i> // Just unlock d->mutex (at the end of the scope) and retry.</i> <span class="local col3 ref" title="d" data-ref="3d">d</span> = <a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::loadAcquire" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZNK19QBasicAtomicPointer11loadAcquireEv" data-ref="_ZNK19QBasicAtomicPointer11loadAcquireEv" data-proj="qtbase">loadAcquire</a>(); <b>continue</b>; } <b>return</b> <span class="local col3 ref" title="d" data-ref="3d">d</span>-><a class="ref" title="QReadWriteLockPrivate::lockForRead" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate11lockForReadEi" data-ref="_ZN21QReadWriteLockPrivate11lockForReadEi" data-proj="qtbase">lockForRead</a>(<span class="local col2 ref" title="timeout" data-ref="2timeout">timeout</span>); } } |
lockForWrite
Exactly the same principle, as lockForRead
but we would also block if there are readers holding the lock.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<em>bool</em> <a class="type" title="QReadWriteLock" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock" data-ref="QReadWriteLock" data-proj="qtbase">QReadWriteLock</a>::<dfn id="_ZN14QReadWriteLock15tryLockForWriteEi" class="decl def" title="QReadWriteLock::tryLockForWrite" data-ref="_ZN14QReadWriteLock15tryLockForWriteEi">tryLockForWrite</dfn>(<em>int</em> <dfn id="7timeout" class="local col7 decl" title="timeout" data-type="int" data-ref="7timeout">timeout</dfn>) { <i>// Fast case: non contended:</i> <a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a> *<dfn id="8d" class="local col8 decl" title="d" data-type="QReadWriteLockPrivate *" data-ref="8d">d</dfn>; <b>if</b> (<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetAcquire" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-proj="qtbase">testAndSetAcquire</a>(<b>nullptr</b>, <span class="tu ref" title="dummyLockedForWrite" data-use="r" data-ref="_ZL19dummyLockedForWrite">dummyLockedForWrite</span>, <span class="refarg"><span class="local col8 ref" title="d" data-ref="8d">d</span></span>)) <b>return</b> <b>true</b>; <b>while</b> (<b>true</b>) { <b>if</b> (<span class="local col8 ref" title="d" data-ref="8d">d</span> == <var>0</var>) { <b>if</b> (!<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetAcquire" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetAcquireEPT_S1_RS1_" data-proj="qtbase">testAndSetAcquire</a>(<span class="local col8 ref" title="d" data-ref="8d">d</span>, <span class="tu ref" title="dummyLockedForWrite" data-use="r" data-ref="_ZL19dummyLockedForWrite">dummyLockedForWrite</span>, <span class="refarg"><span class="local col8 ref" title="d" data-ref="8d">d</span></span>)) <b>continue</b>; <b>return</b> <b>true</b>; } <b>if</b> (<span class="ref" title="isUncontendedLocked" data-ref="_Z19isUncontendedLockedPK21QReadWriteLockPrivate">isUncontendedLocked</span>(<span class="local col8 ref" title="d" data-ref="8d">d</span>)) { <b>if</b> (!<span class="local col7 ref" title="timeout" data-ref="7timeout">timeout</span>) <b>return</b> <b>false</b>; <i>// locked for either read or write, assign a d_ptr and wait.</i> <em>auto</em> <dfn id="9val" class="local col9 decl" title="val" data-type="QReadWriteLockPrivate *" data-ref="9val">val</dfn> = <a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a>::<a class="ref" title="QReadWriteLockPrivate::allocate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate8allocateEv" data-ref="_ZN21QReadWriteLockPrivate8allocateEv" data-proj="qtbase">allocate</a>(); <b>if</b> (<span class="local col8 ref" title="d" data-ref="8d">d</span> == <span class="tu ref" title="dummyLockedForWrite" data-use="r" data-ref="_ZL19dummyLockedForWrite">dummyLockedForWrite</span>) <span class="local col9 ref" title="val" data-ref="9val">val</span>-><a class="ref" title="QReadWriteLockPrivate::writerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::writerCount" data-ref="QReadWriteLockPrivate::writerCount" data-proj="qtbase">writerCount</a> = <var>1</var>; <b>else</b> <span class="local col9 ref" title="val" data-ref="9val">val</span>-><a class="ref" title="QReadWriteLockPrivate::readerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::readerCount" data-ref="QReadWriteLockPrivate::readerCount" data-proj="qtbase">readerCount</a> = (<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="local col8 ref" title="d" data-ref="8d">d</span>) >> <var>4</var>) + <var>1</var>; <b>if</b> (!<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetOrdered" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetOrderedEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetOrderedEPT_S1_RS1_" data-proj="qtbase">testAndSetOrdered</a>(<span class="local col8 ref" title="d" data-ref="8d">d</span>, <span class="local col9 ref" title="val" data-ref="9val">val</span>, <span class="refarg"><span class="local col8 ref" title="d" data-ref="8d">d</span></span>)) { <span class="local col9 ref" title="val" data-ref="9val">val</span>-><a class="ref" title="QReadWriteLockPrivate::writerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::writerCount" data-ref="QReadWriteLockPrivate::writerCount" data-proj="qtbase">writerCount</a> = <span class="local col9 ref" title="val" data-ref="9val">val</span>-><a class="ref" title="QReadWriteLockPrivate::readerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::readerCount" data-ref="QReadWriteLockPrivate::readerCount" data-proj="qtbase">readerCount</a> = <var>0</var>; <span class="local col9 ref" title="val" data-ref="9val">val</span>-><a class="ref" title="QReadWriteLockPrivate::release" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate7releaseEv" data-ref="_ZN21QReadWriteLockPrivate7releaseEv" data-proj="qtbase">release</a>(); <b>continue</b>; } <span class="local col8 ref" title="d" data-ref="8d">d</span> = <span class="local col9 ref" title="val" data-ref="9val">val</span>; } <a class="macro" title="((!(!isUncontendedLocked(d))) ? qt_assert("!isUncontendedLocked(d)","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",110) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#714" data-ref="_M/Q_ASSERT" data-proj="qtbase">Q_ASSERT</a>(!<span class="ref" title="isUncontendedLocked" data-ref="_Z19isUncontendedLockedPK21QReadWriteLockPrivate">isUncontendedLocked</span>(<span class="local col8 ref" title="d" data-ref="8d">d</span>)); <i>// d is an actual pointer;</i> <b>if</b> (<span class="local col8 ref" title="d" data-ref="8d">d</span>-><a class="ref" title="QReadWriteLockPrivate::recursive" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::recursive" data-ref="QReadWriteLockPrivate::recursive" data-proj="qtbase">recursive</a>) <b>return</b> <span class="local col8 ref" title="d" data-ref="8d">d</span>-><a class="ref" title="QReadWriteLockPrivate::recursiveLockForWrite" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate21recursiveLockForWriteEi" data-ref="_ZN21QReadWriteLockPrivate21recursiveLockForWriteEi" data-proj="qtbase">recursiveLockForWrite</a>(<span class="local col7 ref" title="timeout" data-ref="7timeout">timeout</span>); <a class="type" title="QMutexLocker" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qmutex.h.html#QMutexLocker" data-ref="QMutexLocker" data-proj="qtbase">QMutexLocker</a> <dfn id="10lock" class="local col0 decl" title="lock" data-type="QMutexLocker" data-ref="10lock">lock</dfn><a class="ref" title="QMutexLocker::QMutexLocker" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qmutex.h.html#_ZN12QMutexLockerC1EP11QBasicMutex" data-ref="_ZN12QMutexLockerC1EP11QBasicMutex" data-proj="qtbase">(</a>&<span class="local col8 ref" title="d" data-ref="8d">d</span>-><a class="ref" title="QReadWriteLockPrivate::mutex" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::mutex" data-ref="QReadWriteLockPrivate::mutex" data-proj="qtbase">mutex</a>); <b>if</b> (<span class="local col8 ref" title="d" data-ref="8d">d</span> != <a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::load" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZNK19QBasicAtomicPointer4loadEv" data-ref="_ZNK19QBasicAtomicPointer4loadEv" data-proj="qtbase">load</a>()) { <i>// The mutex was unlocked before we had time to lock the mutex.</i> <i> // We are holding to a mutex within a QReadWriteLockPrivate that is already released</i> <i> // (or even is already re-used) but that's ok because the QFreeList never frees them.</i> <span class="local col8 ref" title="d" data-ref="8d">d</span> = <a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::loadAcquire" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZNK19QBasicAtomicPointer11loadAcquireEv" data-ref="_ZNK19QBasicAtomicPointer11loadAcquireEv" data-proj="qtbase">loadAcquire</a>(); <b>continue</b>; } <b>return</b> <span class="local col8 ref" title="d" data-ref="8d">d</span>-><a class="ref" title="QReadWriteLockPrivate::lockForWrite" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate12lockForWriteEi" data-ref="_ZN21QReadWriteLockPrivate12lockForWriteEi" data-proj="qtbase">lockForWrite</a>(<span class="local col7 ref" title="timeout" data-ref="7timeout">timeout</span>); } } |
unlock
The API has a single unlock
for both read and write so we don’t know if we are unlocking from reading or writing. Fortunately, we can know that with the state encoded in the lower bits. If we were locked for read, we need to decrease the reader count, or set the state to 0x0
if we are the last one. If we were locked for write we need to set the state to 0x0
. If there is a QReadWriteLockPrivate
, we need to update the data there, and possibly wake up the blocked threads.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
<em>void</em> <a class="type" title="QReadWriteLock" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock" data-ref="QReadWriteLock" data-proj="qtbase">QReadWriteLock</a>::<dfn id="_ZN14QReadWriteLock6unlockEv" class="decl def" title="QReadWriteLock::unlock" data-ref="_ZN14QReadWriteLock6unlockEv">unlock</dfn>() { <a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a> *<dfn id="11d" class="local col1 decl" title="d" data-type="QReadWriteLockPrivate *" data-ref="11d">d</dfn> = <a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::load" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZNK19QBasicAtomicPointer4loadEv" data-ref="_ZNK19QBasicAtomicPointer4loadEv" data-proj="qtbase">load</a>(); <b>while</b> (<b>true</b>) { <a class="macro" title="((!(d)) ? qt_assert_x("QReadWriteLock::unlock()", "Cannot unlock an unlocked lock","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",134) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#731" data-ref="_M/Q_ASSERT_X" data-proj="qtbase">Q_ASSERT_X</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>, <q>"QReadWriteLock::unlock()"</q>, <q>"Cannot unlock an unlocked lock"</q>); <i>// Fast case: no contention: (no waiters, no other readers)</i> <b>if</b> (<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>) <= <var>2</var>) { <i>// 1 or 2 (StateLockedForRead or StateLockedForWrite)</i> <b>if</b> (!<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetRelease" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetReleaseEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetReleaseEPT_S1_RS1_" data-proj="qtbase">testAndSetRelease</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>, <b>nullptr</b>, <span class="refarg"><span class="local col1 ref" title="d" data-ref="11d">d</span></span>)) <b>continue</b>; <b>return</b>; } <b>if</b> ((<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>) & <span class="enum" title="StateMask" data-ref="StateMask">StateMask</span>) == <span class="enum" title="StateLockedForRead" data-ref="StateLockedForRead">StateLockedForRead</span>) { <a class="macro" title="((!(quintptr(d) > (1U<<4))) ? qt_assert("quintptr(d) > (1U<<4)","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",144) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#714" data-ref="_M/Q_ASSERT" data-proj="qtbase">Q_ASSERT</a>(<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>) > (<var>1U</var><<<var>4</var>)); <i>//otherwise that would be the fast case</i> <i>// Just decrease the reader's count.</i> <em>auto</em> <dfn id="12val" class="local col2 decl" title="val" data-type="QReadWriteLockPrivate *" data-ref="12val">val</dfn> = <b>reinterpret_cast</b><<a class="type" title="QReadWriteLockPrivate" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate" data-ref="QReadWriteLockPrivate" data-proj="qtbase">QReadWriteLockPrivate</a> *>(<a class="typedef" style="color: #800080; border: none; text-decoration: none;" title="quintptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#quintptr" data-type="QIntegerForSizeof<void *>::Unsigned" data-ref="quintptr" data-proj="qtbase">quintptr</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>) - (<var>1U</var><<<var>4</var>)); <b>if</b> (!<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::testAndSetRelease" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer17testAndSetReleaseEPT_S1_RS1_" data-ref="_ZN19QBasicAtomicPointer17testAndSetReleaseEPT_S1_RS1_" data-proj="qtbase">testAndSetRelease</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>, <span class="local col2 ref" title="val" data-ref="12val">val</span>, <span class="refarg"><span class="local col1 ref" title="d" data-ref="11d">d</span></span>)) <b>continue</b>; <b>return</b>; } <a class="macro" title="((!(!isUncontendedLocked(d))) ? qt_assert("!isUncontendedLocked(d)","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",152) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#714" data-ref="_M/Q_ASSERT" data-proj="qtbase">Q_ASSERT</a>(!<span class="ref" title="isUncontendedLocked" data-ref="_Z19isUncontendedLockedPK21QReadWriteLockPrivate">isUncontendedLocked</span>(<span class="local col1 ref" title="d" data-ref="11d">d</span>)); <b>if</b> (<span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::recursive" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::recursive" data-ref="QReadWriteLockPrivate::recursive" data-proj="qtbase">recursive</a>) { <span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::recursiveUnlock" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate15recursiveUnlockEv" data-ref="_ZN21QReadWriteLockPrivate15recursiveUnlockEv" data-proj="qtbase">recursiveUnlock</a>(); <b>return</b>; } <a class="type" title="QMutexLocker" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qmutex.h.html#QMutexLocker" data-ref="QMutexLocker" data-proj="qtbase">QMutexLocker</a> <dfn id="13locker" class="local col3 decl" title="locker" data-type="QMutexLocker" data-ref="13locker">locker</dfn><a class="ref" title="QMutexLocker::QMutexLocker" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qmutex.h.html#_ZN12QMutexLockerC1EP11QBasicMutex" data-ref="_ZN12QMutexLockerC1EP11QBasicMutex" data-proj="qtbase">(</a>&<span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::mutex" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::mutex" data-ref="QReadWriteLockPrivate::mutex" data-proj="qtbase">mutex</a>); <b>if</b> (<span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::writerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::writerCount" data-ref="QReadWriteLockPrivate::writerCount" data-proj="qtbase">writerCount</a>) { <a class="macro" style="color: #000080; border: none; text-decoration: none; font-style: normal;" title="((!(d->writerCount == 1)) ? qt_assert("d->writerCount == 1","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",161) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#714" data-ref="_M/Q_ASSERT" data-proj="qtbase">Q_ASSERT</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::writerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::writerCount" data-ref="QReadWriteLockPrivate::writerCount" data-proj="qtbase">writerCount</a> == <var>1</var>); <a class="macro" style="color: #000080; border: none; text-decoration: none; font-style: normal;" title="((!(d->readerCount == 0)) ? qt_assert("d->readerCount == 0","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",162) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#714" data-ref="_M/Q_ASSERT" data-proj="qtbase">Q_ASSERT</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::readerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::readerCount" data-ref="QReadWriteLockPrivate::readerCount" data-proj="qtbase">readerCount</a> == <var>0</var>); <span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::writerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::writerCount" data-ref="QReadWriteLockPrivate::writerCount" data-proj="qtbase">writerCount</a> = <var>0</var>; } <b>else</b> { <a class="macro" style="color: #000080; border: none; text-decoration: none; font-style: normal;" title="((!(d->readerCount > 0)) ? qt_assert("d->readerCount > 0","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",165) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#714" data-ref="_M/Q_ASSERT" data-proj="qtbase">Q_ASSERT</a>(<span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::readerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::readerCount" data-ref="QReadWriteLockPrivate::readerCount" data-proj="qtbase">readerCount</a> > <var>0</var>); <span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::readerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::readerCount" data-ref="QReadWriteLockPrivate::readerCount" data-proj="qtbase">readerCount</a>--; <b>if</b> (<span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::readerCount" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::readerCount" data-ref="QReadWriteLockPrivate::readerCount" data-proj="qtbase">readerCount</a> > <var>0</var>) <b>return</b>; } <b>if</b> (<span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::waitingReaders" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::waitingReaders" data-ref="QReadWriteLockPrivate::waitingReaders" data-proj="qtbase">waitingReaders</a> || <span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::waitingWriters" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#QReadWriteLockPrivate::waitingWriters" data-ref="QReadWriteLockPrivate::waitingWriters" data-proj="qtbase">waitingWriters</a>) { <span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::unlock" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate6unlockEv" data-ref="_ZN21QReadWriteLockPrivate6unlockEv" data-proj="qtbase">unlock</a>(); } <b>else</b> { <a class="macro" title="((!(d_ptr.load() == d)) ? qt_assert("d_ptr.load() == d","/home/olivier/woboq/web/woboqwebsite/data/blogs/qreadwritelock-gets-faster-in-qt57.data/qreadwritelock.cpp",174) : qt_noop())" href="https://code.woboq.org/qt5/qtbase/src/corelib/global/qglobal.h.html#714" data-ref="_M/Q_ASSERT" data-proj="qtbase">Q_ASSERT</a>(<a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::load" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZNK19QBasicAtomicPointer4loadEv" data-ref="_ZNK19QBasicAtomicPointer4loadEv" data-proj="qtbase">load</a>() == <span class="local col1 ref" title="d" data-ref="11d">d</span>); <i>// should not change when we still hold the mutex</i> <a class="member" title="QReadWriteLock::d_ptr" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock.h.html#QReadWriteLock::d_ptr" data-ref="QReadWriteLock::d_ptr" data-proj="qtbase">d_ptr</a>.<a class="ref" title="QBasicAtomicPointer::storeRelease" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qbasicatomic.h.html#_ZN19QBasicAtomicPointer12storeReleaseEPT_" data-ref="_ZN19QBasicAtomicPointer12storeReleaseEPT_" data-proj="qtbase">storeRelease</a>(<b>nullptr</b>); <span class="local col1 ref" title="d" data-ref="11d">d</span>-><a class="ref" title="QReadWriteLockPrivate::release" href="https://code.woboq.org/qt5/qtbase/src/corelib/thread/qreadwritelock_p.h.html#_ZN21QReadWriteLockPrivate7releaseEv" data-ref="_ZN21QReadWriteLockPrivate7releaseEv" data-proj="qtbase">release</a>(); } <b>return</b>; } } |
Benchmarks
Here is the benchmark that was run: https://codereview.qt-project.org/167113/. The benchmark was run with Qt 5.6.1, GCC 6.1.1. What I call Qt 5.7 bellow is in fact Qt 5.6 + the QReadWriteLock patch so it only compares this patch.
Uncontended
This benchmark compares different types of lock by having a single thread running in a loop 1000000 times, locking and unlocking the mutex and doing nothing else.
QReadWriteLock (Qt 5.6) | 38 ms | ███████████████████ |
QReadWriteLock (Qt 5.7) | 18 ms | █████████ |
QMutex | 16 ms | ████████ |
std::mutex | 18 ms | █████████ |
std::shared_timed_mutex | 33 ms | ████████████████▌ |
Contented Reads
This benchmark runs as much threads as logical cores (4 in my cases). Each thread will lock and unlock the same mutex 1000000 times. We do a small amount of work inside and outside the lock. If no other work was done at all and the threads were only locking and unlocking, we would have a huge pressure on the mutex but this would not be a fair benchmark. So this benchmark does a hash lookup inside the lock and a string allocation outside of the lock. The more work is done inside the lock, the more we disadvantage QMutex compared to QReadWriteLock because threads would be blocked for longer time.
QReadWriteLock (Qt 5.6) | 812 ms | ████████████████████▍ |
QReadWriteLock (Qt 5.7) | 285 ms | ███████▏ |
QMutex | 398 ms | ██████████ |
std::mutex | 489 ms | ████████████▎ |
std::shared_timed_mutex | 811 ms | ████████████████████▎ |
Futex Version
On platforms that have futexes, QMutex does not even need a QMutexPrivate, it uses the futexes to hold the lock. Similarly, we could do the same with QReadWriteLock. I made an implementation of QReadWriteLock using futex (in fact I made it first before the generic version). But it is not in Qt 5.7 and is not yet merged in Qt, perhaps for a future version if I get the motivation and time to get it merged.
Could we get even faster?
As always, nothing is perfect and there is always still room for improvement. A flaw of this implementation is that all the readers still need to perform an atomic write at the same memory location (in order to increment the reader’s count). This causes contention if there are many reader threads. For cache performance, we would no want that the readers write to the same memory location. Such implementations are possible and would make the contended case faster, but then would take more memory and might be slower for the non-contended case.
Conclusion
These benchmarks shows the huge improvement in QReadWriteLock in Qt 5.7. The Qt classes have nothing to envy to their libstdc++ implementation. std::shared_timed_mutex
which would be the standard equivalent of a QReadWriteLock is surprisingly slow. (I heard rumors that it might get better.)
It is optimized for the usual case of Qt with relatively low contention. It is taking a very small amount of memory and makes it a pretty decent implementation of a read write lock.
In summary, you can now use QReadWriteLock as soon as there are many reads and seldom writes. This is only about non recursive mutex. Recursive mutex are always slower and should be avoided. Not only because they are slower, but it is also harder to reason about them.