Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qreadwritelock.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5// Qt-Security score:significant reason:default
6
7#include "qplatformdefs.h"
9
10#include "qthread.h"
12#include "private/qfreelist_p.h"
13#include "private/qlocking_p.h"
14
15#include <algorithm>
16
17QT_BEGIN_NAMESPACE
18
19/*
20 * Implementation details of QReadWriteLock:
21 *
22 * Depending on the valued of d_ptr, the lock is in the following state:
23 * - when d_ptr == 0x0: Unlocked (no readers, no writers) and non-recursive.
24 * - when d_ptr & 0x1: If the least significant bit is set, we are locked for read.
25 * In that case, d_ptr>>4 represents the number of reading threads minus 1. No writers
26 * are waiting, and the lock is not recursive.
27 * - when d_ptr == 0x2: We are locked for write and nobody is waiting. (no contention)
28 * - In any other case, d_ptr points to an actual QReadWriteLockPrivate.
29 */
30
31using namespace QReadWriteLockStates;
32namespace {
33
34using steady_clock = std::chrono::steady_clock;
35
36const auto dummyLockedForRead = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(StateLockedForRead));
37const auto dummyLockedForWrite = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(StateLockedForWrite));
38inline bool isUncontendedLocked(const QReadWriteLockPrivate *d)
39{ return quintptr(d) & StateMask; }
40}
41
42/*! \class QReadWriteLock
43 \inmodule QtCore
44 \brief The QReadWriteLock class provides read-write locking.
45
46 \threadsafe
47
48 \ingroup thread
49
50 A read-write lock is a synchronization tool for protecting
51 resources that can be accessed for reading and writing. This type
52 of lock is useful if you want to allow multiple threads to have
53 simultaneous read-only access, but as soon as one thread wants to
54 write to the resource, all other threads must be blocked until
55 the writing is complete.
56
57 In many cases, QReadWriteLock is a direct competitor to QMutex.
58 QReadWriteLock is a good choice if there are many concurrent
59 reads and writing occurs infrequently.
60
61 Example:
62
63 \snippet code/src_corelib_thread_qreadwritelock.cpp lock
64 \snippet code/src_corelib_thread_qreadwritelock.cpp 0
65
66 To ensure that writers aren't blocked forever by readers, readers
67 attempting to obtain a lock will not succeed if there is a blocked
68 writer waiting for access, even if the lock is currently only
69 accessed by other readers. Also, if the lock is accessed by a
70 writer and another writer comes in, that writer will have
71 priority over any readers that might also be waiting.
72
73 Like QMutex, a QReadWriteLock can be recursively locked by the
74 same thread when constructed with \l{QReadWriteLock::Recursive} as
75 \l{QReadWriteLock::RecursionMode}. In such cases,
76 unlock() must be called the same number of times lockForWrite() or
77 lockForRead() was called. Note that the lock type cannot be
78 changed when trying to lock recursively, i.e. it is not possible
79 to lock for reading in a thread that already has locked for
80 writing (and vice versa).
81
82 \sa QReadLocker, QWriteLocker, QMutex, QSemaphore
83*/
84
85/*!
86 \enum QReadWriteLock::RecursionMode
87 \since 4.4
88
89 \value Recursive In this mode, a thread can lock the same
90 QReadWriteLock multiple times. The QReadWriteLock won't be unlocked
91 until a corresponding number of unlock() calls have been made.
92
93 \value NonRecursive In this mode, a thread may only lock a
94 QReadWriteLock once.
95
96 \sa QReadWriteLock()
97*/
98
99/*!
100 \fn QReadWriteLock::QReadWriteLock(RecursionMode recursionMode)
101 \since 4.4
102
103 Constructs a QReadWriteLock object in the given \a recursionMode.
104
105 The default recursion mode is NonRecursive.
106
107 \sa lockForRead(), lockForWrite(), RecursionMode
108*/
109QReadWriteLockPrivate *QReadWriteLock::initRecursive()
110{
111 auto d = new QReadWriteLockPrivate(true);
112 Q_ASSERT_X(!(quintptr(d) & StateMask), "QReadWriteLock::QReadWriteLock", "bad d_ptr alignment");
113 return d;
114}
115
116/*!
117 \fn QReadWriteLock::~QReadWriteLock()
118 Destroys the QReadWriteLock object.
119
120 \warning Destroying a read-write lock that is in use may result
121 in undefined behavior.
122*/
123void QReadWriteLock::destroyRecursive(QReadWriteLockPrivate *d)
124{
125 if (isUncontendedLocked(d)) {
126 qWarning("QReadWriteLock: destroying locked QReadWriteLock");
127 return;
128 }
129 delete d;
130}
131
132/*!
133 \fn QReadWriteLock::lockForRead()
134 Locks the lock for reading. This function will block the current
135 thread if another thread has locked for writing.
136
137 It is not possible to lock for read if the thread already has
138 locked for write.
139
140 \sa unlock(), lockForWrite(), tryLockForRead()
141*/
142
143/*!
144 \fn bool QReadWriteLock::tryLockForRead(int timeout)
145
146 Attempts to lock for reading. This function returns \c true if the
147 lock was obtained; otherwise it returns \c false. If another thread
148 has locked for writing, this function will wait for at most \a
149 timeout milliseconds for the lock to become available.
150
151 Note: Passing a negative number as the \a timeout is equivalent to
152 calling lockForRead(), i.e. this function will wait forever until
153 lock can be locked for reading when \a timeout is negative.
154
155 If the lock was obtained, the lock must be unlocked with unlock()
156 before another thread can successfully lock it for writing.
157
158 It is not possible to lock for read if the thread already has
159 locked for write.
160
161 \sa unlock(), lockForRead()
162*/
163
164static Q_ALWAYS_INLINE bool fastTryLock(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
165 QReadWriteLockPrivate *dummyValue,
167{
168 // Succeed fast if not contended
169 return d == nullptr && d_ptr.testAndSetAcquire(nullptr, dummyValue, d);
170}
171
172/*!
173 \fn bool QReadWriteLock::tryLockForRead(QDeadlineTimer timeout)
174 \overload
175 \since 6.6
176
177 Attempts to lock for reading. This function returns \c true if the lock was
178 obtained; otherwise it returns \c false. If another thread has locked for
179 writing, this function will wait until \a timeout expires for the lock to
180 become available.
181
182 If the lock was obtained, the lock must be unlocked with unlock()
183 before another thread can successfully lock it for writing.
184
185 It is not possible to lock for read if the thread already has
186 locked for write.
187
188 \sa unlock(), lockForRead()
189*/
190
191Q_NEVER_INLINE bool
192QBasicReadWriteLock::contendedTryLockForRead(QDeadlineTimer timeout, void *dd)
193{
194 auto d = static_cast<QReadWriteLockPrivate *>(dd);
195 while (true) {
196 qYieldCpu();
197 if (d == nullptr) {
198 if (fastTryLock(d_ptr, dummyLockedForRead, d))
199 return true;
200 continue;
201 }
202
203 if ((quintptr(d) & StateMask) == StateLockedForRead) {
204 // locked for read, increase the counter
205 const auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) + Counter);
206 Q_ASSERT_X(quintptr(val) > Counter, "QReadWriteLock::tryLockForRead()",
207 "Overflow in lock counter");
208 if (!d_ptr.testAndSetAcquire(d, val, d))
209 continue;
210 return true;
211 }
212
213 if (d == dummyLockedForWrite) {
214 if (timeout.hasExpired())
215 return false;
216
217 // locked for write, assign a d_ptr and wait.
218 auto val = QReadWriteLockPrivate::allocate();
219 val->writerCount = 1;
220 if (!d_ptr.testAndSetOrdered(d, val, d)) {
221 val->writerCount = 0;
222 val->release();
223 continue;
224 }
225 d = val;
226 }
227 Q_ASSERT(!isUncontendedLocked(d));
228 // d is an actual pointer; acquire its contents
229 d = d_ptr.loadAcquire();
230 if (!d || isUncontendedLocked(d))
231 continue;
232
233 if (d->recursive)
234 return d->recursiveLockForRead(timeout);
235
236 auto lock = qt_unique_lock(d->mutex);
237 if (d != d_ptr.loadRelaxed()) {
238 // d_ptr has changed: this QReadWriteLock was unlocked before we had
239 // time to lock d->mutex.
240 // We are holding a lock to a mutex within a QReadWriteLockPrivate
241 // that is already released (or even is already re-used). That's ok
242 // because the QFreeList never frees them.
243 // Just unlock d->mutex (at the end of the scope) and retry.
244 d = d_ptr.loadAcquire();
245 continue;
246 }
247 return d->lockForRead(lock, timeout);
248 }
249}
250
251/*!
252 \fn QReadWriteLock::lockForWrite()
253 Locks the lock for writing. This function will block the current
254 thread if another thread (including the current) has locked for
255 reading or writing (unless the lock has been created using the
256 \l{QReadWriteLock::Recursive} mode).
257
258 It is not possible to lock for write if the thread already has
259 locked for read.
260
261 \sa unlock(), lockForRead(), tryLockForWrite()
262*/
263
264/*!
265 \fn QReadWriteLock::tryLockForWrite(int timeout)
266
267 Attempts to lock for writing. This function returns \c true if the
268 lock was obtained; otherwise it returns \c false. If another thread
269 has locked for reading or writing, this function will wait for at
270 most \a timeout milliseconds for the lock to become available.
271
272 Note: Passing a negative number as the \a timeout is equivalent to
273 calling lockForWrite(), i.e. this function will wait forever until
274 lock can be locked for writing when \a timeout is negative.
275
276 If the lock was obtained, the lock must be unlocked with unlock()
277 before another thread can successfully lock it.
278
279 It is not possible to lock for write if the thread already has
280 locked for read.
281
282 \sa unlock(), lockForWrite()
283*/
284
285/*!
286 \fn bool QReadWriteLock::tryLockForWrite(QDeadlineTimer timeout)
287 \overload
288 \since 6.6
289
290 Attempts to lock for writing. This function returns \c true if the lock was
291 obtained; otherwise it returns \c false. If another thread has locked for
292 reading or writing, this function will wait until \a timeout expires for
293 the lock to become available.
294
295 If the lock was obtained, the lock must be unlocked with unlock()
296 before another thread can successfully lock it.
297
298 It is not possible to lock for write if the thread already has
299 locked for read.
300
301 \sa unlock(), lockForWrite()
302*/
303
304Q_NEVER_INLINE bool
305QBasicReadWriteLock::contendedTryLockForWrite(QDeadlineTimer timeout, void *dd)
306{
307 auto d = static_cast<QReadWriteLockPrivate *>(dd);
308 while (true) {
309 qYieldCpu();
310 if (d == nullptr) {
311 if (fastTryLock(d_ptr, dummyLockedForWrite, d))
312 return true;
313 continue;
314 }
315
316 if (isUncontendedLocked(d)) {
317 if (timeout.hasExpired())
318 return false;
319
320 // locked for either read or write, assign a d_ptr and wait.
321 auto val = QReadWriteLockPrivate::allocate();
322 if (d == dummyLockedForWrite)
323 val->writerCount = 1;
324 else
325 val->readerCount = (quintptr(d) >> 4) + 1;
326 if (!d_ptr.testAndSetOrdered(d, val, d)) {
327 val->writerCount = val->readerCount = 0;
328 val->release();
329 continue;
330 }
331 d = val;
332 }
333 Q_ASSERT(!isUncontendedLocked(d));
334 // d is an actual pointer; acquire its contents
335 d = d_ptr.loadAcquire();
336 if (!d || isUncontendedLocked(d))
337 continue;
338
339 if (d->recursive)
340 return d->recursiveLockForWrite(timeout);
341
342 auto lock = qt_unique_lock(d->mutex);
343 if (d != d_ptr.loadRelaxed()) {
344 // The mutex was unlocked before we had time to lock the mutex.
345 // We are holding to a mutex within a QReadWriteLockPrivate that is already released
346 // (or even is already re-used) but that's ok because the QFreeList never frees them.
347 d = d_ptr.loadAcquire();
348 continue;
349 }
350 return d->lockForWrite(lock, timeout);
351 }
352}
353
354/*!
355 \fn void QReadWriteLock::unlock()
356 Unlocks the lock.
357
358 Attempting to unlock a lock that is not locked is an error, and will result
359 in program termination.
360
361 \sa lockForRead(), lockForWrite(), tryLockForRead(), tryLockForWrite()
362*/
363
364void QBasicReadWriteLock::contendedUnlock(void *dd)
365{
366 auto d = static_cast<QReadWriteLockPrivate *>(dd);
367 while (true) {
368 Q_ASSERT_X(d, "QReadWriteLock::unlock()", "Cannot unlock an unlocked lock");
369
370 // Fast case: no contention: (no waiters, no other readers)
371 if (quintptr(d) <= 2) { // 1 or 2 (StateLockedForRead or StateLockedForWrite)
372 if (!d_ptr.testAndSetOrdered(d, nullptr, d))
373 continue;
374 return;
375 }
376
377 if ((quintptr(d) & StateMask) == StateLockedForRead) {
378 Q_ASSERT(quintptr(d) > Counter); //otherwise that would be the fast case
379 // Just decrease the reader's count.
380 auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) - (1U<<4));
381 if (!d_ptr.testAndSetOrdered(d, val, d))
382 continue;
383 return;
384 }
385
386 Q_ASSERT(!isUncontendedLocked(d));
387
388 if (d->recursive) {
389 d->recursiveUnlock();
390 return;
391 }
392
393 const auto lock = qt_scoped_lock(d->mutex);
394 if (d->writerCount) {
395 Q_ASSERT(d->writerCount == 1);
396 Q_ASSERT(d->readerCount == 0);
397 d->writerCount = 0;
398 } else {
399 Q_ASSERT(d->readerCount > 0);
400 d->readerCount--;
401 if (d->readerCount > 0)
402 return;
403 }
404
405 if (d->waitingReaders || d->waitingWriters) {
406 d->unlock();
407 } else {
408 Q_ASSERT(d_ptr.loadRelaxed() == d); // should not change when we still hold the mutex
409 d_ptr.storeRelease(nullptr);
410 d->release();
411 }
412 return;
413 }
414}
415
416bool QReadWriteLockPrivate::lockForRead(std::unique_lock<std::mutex> &lock, QDeadlineTimer timeout)
417{
418 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
419
420 while (waitingWriters || writerCount) {
421 if (timeout.hasExpired())
422 return false;
423 if (!timeout.isForever()) {
425 readerCond.wait_until(lock, timeout.deadline<steady_clock>());
426 } else {
428 readerCond.wait(lock);
429 }
431 }
432 readerCount++;
433 Q_ASSERT(writerCount == 0);
434 return true;
435}
436
437bool QReadWriteLockPrivate::lockForWrite(std::unique_lock<std::mutex> &lock, QDeadlineTimer timeout)
438{
439 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
440
441 while (readerCount || writerCount) {
442 if (timeout.hasExpired()) {
444 // We timed out and now there is no more writers or waiting writers, but some
445 // readers were queued (probably because of us). Wake the waiting readers.
446 readerCond.notify_all();
447 }
448 return false;
449 }
450 if (!timeout.isForever()) {
452 writerCond.wait_until(lock, timeout.deadline<steady_clock>());
453 } else {
455 writerCond.wait(lock);
456 }
458 }
459
460 Q_ASSERT(writerCount == 0);
461 Q_ASSERT(readerCount == 0);
462 writerCount = 1;
463 return true;
464}
465
467{
468 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
469 if (waitingWriters)
470 writerCond.notify_one();
471 else if (waitingReaders)
472 readerCond.notify_all();
473}
474
475static auto handleEquals(Qt::HANDLE handle)
476{
477 return [handle](QReadWriteLockPrivate::Reader reader) { return reader.handle == handle; };
478}
479
480bool QReadWriteLockPrivate::recursiveLockForRead(QDeadlineTimer timeout)
481{
482 Q_ASSERT(recursive);
483 auto lock = qt_unique_lock(mutex);
484
485 Qt::HANDLE self = QThread::currentThreadId();
486
487 auto it = std::find_if(currentReaders.begin(), currentReaders.end(),
488 handleEquals(self));
489 if (it != currentReaders.end()) {
490 ++it->recursionLevel;
491 return true;
492 }
493
494 if (!lockForRead(lock, timeout))
495 return false;
496
497 Reader r = {self, 1};
498 currentReaders.append(std::move(r));
499 return true;
500}
501
502bool QReadWriteLockPrivate::recursiveLockForWrite(QDeadlineTimer timeout)
503{
504 Q_ASSERT(recursive);
505 auto lock = qt_unique_lock(mutex);
506
507 Qt::HANDLE self = QThread::currentThreadId();
508 if (currentWriter == self) {
509 writerCount++;
510 return true;
511 }
512
513 if (!lockForWrite(lock, timeout))
514 return false;
515
516 currentWriter = self;
517 return true;
518}
519
521{
522 Q_ASSERT(recursive);
523 auto lock = qt_unique_lock(mutex);
524
525 Qt::HANDLE self = QThread::currentThreadId();
526 if (self == currentWriter) {
527 if (--writerCount > 0)
528 return;
529 currentWriter = nullptr;
530 } else {
531 auto it = std::find_if(currentReaders.begin(), currentReaders.end(),
532 handleEquals(self));
533 if (it == currentReaders.end()) {
534 qWarning("QReadWriteLock::unlock: unlocking from a thread that did not lock");
535 return;
536 } else {
537 if (--it->recursionLevel <= 0) {
538 currentReaders.erase(it);
539 readerCount--;
540 }
541 if (readerCount)
542 return;
543 }
544 }
545
546 unlock();
547}
548
549// The freelist management
550namespace {
551struct QReadWriteLockFreeListConstants : QFreeListDefaultConstants
552{
553 enum { BlockCount = 4, MaxIndex=0xffff };
554 static const int Sizes[BlockCount];
555};
556Q_CONSTINIT const int
557 QReadWriteLockFreeListConstants::Sizes[QReadWriteLockFreeListConstants::BlockCount] = {
558 16, 128, 1024, QReadWriteLockFreeListConstants::MaxIndex - (16 + 128 + 1024)
559 };
560
561typedef QFreeList<QReadWriteLockPrivate, QReadWriteLockFreeListConstants> QReadWriteLockFreeList;
562Q_GLOBAL_STATIC(QReadWriteLockFreeList, qrwl_freelist);
563}
564
566{
567 int i = qrwl_freelist->next();
568 QReadWriteLockPrivate *d = &(*qrwl_freelist)[i];
569 d->id = i;
570 Q_ASSERT(!d->recursive);
572 return d;
573}
574
576{
577 Q_ASSERT(!recursive);
579 qrwl_freelist->release(id);
580}
581
582/*!
583 \class QReadLocker
584 \inmodule QtCore
585 \brief The QReadLocker class is a convenience class that
586 simplifies locking and unlocking read-write locks for read access.
587
588 \threadsafe
589
590 \ingroup thread
591
592 The purpose of QReadLocker (and QWriteLocker) is to simplify
593 QReadWriteLock locking and unlocking. Locking and unlocking
594 statements or in exception handling code is error-prone and
595 difficult to debug. QReadLocker can be used in such situations
596 to ensure that the state of the lock is always well-defined.
597
598 Here's an example that uses QReadLocker to lock and unlock a
599 read-write lock for reading:
600
601 \snippet code/src_corelib_thread_qreadwritelock.cpp lock
602 \snippet code/src_corelib_thread_qreadwritelock.cpp 1
603
604 It is equivalent to the following code:
605
606 \snippet code/src_corelib_thread_qreadwritelock.cpp lock
607 \snippet code/src_corelib_thread_qreadwritelock.cpp 2
608
609 The QMutexLocker documentation shows examples where the use of a
610 locker object greatly simplifies programming.
611
612 \sa QWriteLocker, QReadWriteLock
613*/
614
615/*!
616 \fn QReadLocker::QReadLocker(QReadWriteLock *lock)
617
618 Constructs a QReadLocker and locks \a lock for reading. The lock
619 will be unlocked when the QReadLocker is destroyed. If \c lock is
620 zero, QReadLocker does nothing.
621
622 \sa QReadWriteLock::lockForRead()
623*/
624
625/*!
626 \fn QReadLocker::~QReadLocker()
627
628 Destroys the QReadLocker and unlocks the lock that was passed to
629 the constructor.
630
631 \sa QReadWriteLock::unlock()
632*/
633
634/*!
635 \fn void QReadLocker::unlock()
636
637 Unlocks the lock associated with this locker.
638
639 \sa QReadWriteLock::unlock()
640*/
641
642/*!
643 \fn void QReadLocker::relock()
644
645 Relocks an unlocked lock.
646
647 \sa unlock()
648*/
649
650/*!
651 \fn QReadWriteLock *QReadLocker::readWriteLock() const
652
653 Returns a pointer to the read-write lock that was passed
654 to the constructor.
655*/
656
657/*!
658 \class QWriteLocker
659 \inmodule QtCore
660 \brief The QWriteLocker class is a convenience class that
661 simplifies locking and unlocking read-write locks for write access.
662
663 \threadsafe
664
665 \ingroup thread
666
667 The purpose of QWriteLocker (and QReadLocker) is to simplify
668 QReadWriteLock locking and unlocking. Locking and unlocking
669 statements or in exception handling code is error-prone and
670 difficult to debug. QWriteLocker can be used in such situations
671 to ensure that the state of the lock is always well-defined.
672
673 Here's an example that uses QWriteLocker to lock and unlock a
674 read-write lock for writing:
675
676 \snippet code/src_corelib_thread_qreadwritelock.cpp lock
677 \snippet code/src_corelib_thread_qreadwritelock.cpp 3
678
679 It is equivalent to the following code:
680
681 \snippet code/src_corelib_thread_qreadwritelock.cpp lock
682 \snippet code/src_corelib_thread_qreadwritelock.cpp 4
683
684 The QMutexLocker documentation shows examples where the use of a
685 locker object greatly simplifies programming.
686
687 \sa QReadLocker, QReadWriteLock
688*/
689
690/*!
691 \fn QWriteLocker::QWriteLocker(QReadWriteLock *lock)
692
693 Constructs a QWriteLocker and locks \a lock for writing. The lock
694 will be unlocked when the QWriteLocker is destroyed. If \c lock is
695 zero, QWriteLocker does nothing.
696
697 \sa QReadWriteLock::lockForWrite()
698*/
699
700/*!
701 \fn QWriteLocker::~QWriteLocker()
702
703 Destroys the QWriteLocker and unlocks the lock that was passed to
704 the constructor.
705
706 \sa QReadWriteLock::unlock()
707*/
708
709/*!
710 \fn void QWriteLocker::unlock()
711
712 Unlocks the lock associated with this locker.
713
714 \sa QReadWriteLock::unlock()
715*/
716
717/*!
718 \fn void QWriteLocker::relock()
719
720 Relocks an unlocked lock.
721
722 \sa unlock()
723*/
724
725/*!
726 \fn QReadWriteLock *QWriteLocker::readWriteLock() const
727
728 Returns a pointer to the read-write lock that was passed
729 to the constructor.
730*/
731
732QT_END_NAMESPACE
std::condition_variable readerCond
bool lockForRead(std::unique_lock< std::mutex > &lock, QDeadlineTimer timeout)
bool lockForWrite(std::unique_lock< std::mutex > &lock, QDeadlineTimer timeout)
bool recursiveLockForRead(QDeadlineTimer timeout)
bool recursiveLockForWrite(QDeadlineTimer timeout)
static QReadWriteLockPrivate * allocate()
std::condition_variable writerCond
static Q_ALWAYS_INLINE bool fastTryLock(QAtomicPointer< QReadWriteLockPrivate > &d_ptr, QReadWriteLockPrivate *dummyValue, QReadWriteLockPrivate *&d)
static auto handleEquals(Qt::HANDLE handle)