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

source code of qtbase/src/corelib/thread/qreadwritelock.cpp