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"
8#include "qreadwritelock.h"
9
10#include "qthread.h"
11#include "qreadwritelock_p.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
168static Q_ALWAYS_INLINE bool fastTryLock(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
169 QReadWriteLockPrivate *dummyValue,
170 QReadWriteLockPrivate *&d)
171{
172 // Succeed fast if not contended
173 return d == nullptr && d_ptr.testAndSetAcquire(expectedValue: nullptr, newValue: dummyValue, currentValue&: d);
174}
175
176/*!
177 \overload
178 \since 6.6
179
180 Attempts to lock for reading. This function returns \c true if the lock was
181 obtained; otherwise it returns \c false. If another thread has locked for
182 writing, this function will wait until \a timeout expires for the lock to
183 become available.
184
185 If the lock was obtained, the lock must be unlocked with unlock()
186 before another thread can successfully lock it for writing.
187
188 It is not possible to lock for read if the thread already has
189 locked for write.
190
191 \sa unlock(), lockForRead()
192*/
193bool QReadWriteLock::tryLockForRead(QDeadlineTimer timeout)
194{
195 QReadWriteLockPrivate *d = d_ptr.loadRelaxed();
196 if (fastTryLock(d_ptr, dummyValue: dummyLockedForRead, d))
197 return true;
198 return contendedTryLockForRead(d_ptr, timeout, d);
199}
200
201Q_NEVER_INLINE static bool contendedTryLockForRead(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
202 QDeadlineTimer timeout, QReadWriteLockPrivate *d)
203{
204 while (true) {
205 qYieldCpu();
206 if (d == nullptr) {
207 if (fastTryLock(d_ptr, dummyValue: dummyLockedForRead, d))
208 return true;
209 continue;
210 }
211
212 if ((quintptr(d) & StateMask) == StateLockedForRead) {
213 // locked for read, increase the counter
214 const auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) + (1U<<4));
215 Q_ASSERT_X(quintptr(val) > (1U<<4), "QReadWriteLock::tryLockForRead()",
216 "Overflow in lock counter");
217 if (!d_ptr.testAndSetAcquire(expectedValue: d, newValue: val, currentValue&: d))
218 continue;
219 return true;
220 }
221
222 if (d == dummyLockedForWrite) {
223 if (timeout.hasExpired())
224 return false;
225
226 // locked for write, assign a d_ptr and wait.
227 auto val = QReadWriteLockPrivate::allocate();
228 val->writerCount = 1;
229 if (!d_ptr.testAndSetOrdered(expectedValue: d, newValue: val, currentValue&: d)) {
230 val->writerCount = 0;
231 val->release();
232 continue;
233 }
234 d = val;
235 }
236 Q_ASSERT(!isUncontendedLocked(d));
237 // d is an actual pointer; acquire its contents
238 d = d_ptr.loadAcquire();
239 if (!d || isUncontendedLocked(d))
240 continue;
241
242 if (d->recursive)
243 return d->recursiveLockForRead(timeout);
244
245 auto lock = qt_unique_lock(mutex&: d->mutex);
246 if (d != d_ptr.loadRelaxed()) {
247 // d_ptr has changed: this QReadWriteLock was unlocked before we had
248 // time to lock d->mutex.
249 // We are holding a lock to a mutex within a QReadWriteLockPrivate
250 // that is already released (or even is already re-used). That's ok
251 // because the QFreeList never frees them.
252 // Just unlock d->mutex (at the end of the scope) and retry.
253 d = d_ptr.loadAcquire();
254 continue;
255 }
256 return d->lockForRead(lock, timeout);
257 }
258}
259
260/*!
261 \fn QReadWriteLock::lockForWrite()
262 Locks the lock for writing. This function will block the current
263 thread if another thread (including the current) has locked for
264 reading or writing (unless the lock has been created using the
265 \l{QReadWriteLock::Recursive} mode).
266
267 It is not possible to lock for write if the thread already has
268 locked for read.
269
270 \sa unlock(), lockForRead(), tryLockForWrite()
271*/
272
273/*!
274 \fn QReadWriteLock::tryLockForWrite(int timeout)
275
276 Attempts to lock for writing. This function returns \c true if the
277 lock was obtained; otherwise it returns \c false. If another thread
278 has locked for reading or writing, this function will wait for at
279 most \a timeout milliseconds for the lock to become available.
280
281 Note: Passing a negative number as the \a timeout is equivalent to
282 calling lockForWrite(), i.e. this function will wait forever until
283 lock can be locked for writing when \a timeout is negative.
284
285 If the lock was obtained, the lock must be unlocked with unlock()
286 before another thread can successfully lock it.
287
288 It is not possible to lock for write if the thread already has
289 locked for read.
290
291 \sa unlock(), lockForWrite()
292*/
293
294/*!
295 \overload
296 \since 6.6
297
298 Attempts to lock for writing. This function returns \c true if the lock was
299 obtained; otherwise it returns \c false. If another thread has locked for
300 reading or writing, this function will wait until \a timeout expires for
301 the lock to become available.
302
303 If the lock was obtained, the lock must be unlocked with unlock()
304 before another thread can successfully lock it.
305
306 It is not possible to lock for write if the thread already has
307 locked for read.
308
309 \sa unlock(), lockForWrite()
310*/
311bool QReadWriteLock::tryLockForWrite(QDeadlineTimer timeout)
312{
313 QReadWriteLockPrivate *d = d_ptr.loadRelaxed();
314 if (fastTryLock(d_ptr, dummyValue: dummyLockedForWrite, d))
315 return true;
316 return contendedTryLockForWrite(d_ptr, timeout, d);
317}
318
319Q_NEVER_INLINE static bool contendedTryLockForWrite(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
320 QDeadlineTimer timeout, QReadWriteLockPrivate *d)
321{
322 while (true) {
323 qYieldCpu();
324 if (d == nullptr) {
325 if (fastTryLock(d_ptr, dummyValue: dummyLockedForWrite, d))
326 return true;
327 continue;
328 }
329
330 if (isUncontendedLocked(d)) {
331 if (timeout.hasExpired())
332 return false;
333
334 // locked for either read or write, assign a d_ptr and wait.
335 auto val = QReadWriteLockPrivate::allocate();
336 if (d == dummyLockedForWrite)
337 val->writerCount = 1;
338 else
339 val->readerCount = (quintptr(d) >> 4) + 1;
340 if (!d_ptr.testAndSetOrdered(expectedValue: d, newValue: val, currentValue&: d)) {
341 val->writerCount = val->readerCount = 0;
342 val->release();
343 continue;
344 }
345 d = val;
346 }
347 Q_ASSERT(!isUncontendedLocked(d));
348 // d is an actual pointer; acquire its contents
349 d = d_ptr.loadAcquire();
350 if (!d || isUncontendedLocked(d))
351 continue;
352
353 if (d->recursive)
354 return d->recursiveLockForWrite(timeout);
355
356 auto lock = qt_unique_lock(mutex&: d->mutex);
357 if (d != d_ptr.loadRelaxed()) {
358 // The mutex was unlocked before we had time to lock the mutex.
359 // We are holding to a mutex within a QReadWriteLockPrivate that is already released
360 // (or even is already re-used) but that's ok because the QFreeList never frees them.
361 d = d_ptr.loadAcquire();
362 continue;
363 }
364 return d->lockForWrite(lock, timeout);
365 }
366}
367
368/*!
369 Unlocks the lock.
370
371 Attempting to unlock a lock that is not locked is an error, and will result
372 in program termination.
373
374 \sa lockForRead(), lockForWrite(), tryLockForRead(), tryLockForWrite()
375*/
376void QReadWriteLock::unlock()
377{
378 QReadWriteLockPrivate *d = d_ptr.loadAcquire();
379 while (true) {
380 Q_ASSERT_X(d, "QReadWriteLock::unlock()", "Cannot unlock an unlocked lock");
381
382 // Fast case: no contention: (no waiters, no other readers)
383 if (quintptr(d) <= 2) { // 1 or 2 (StateLockedForRead or StateLockedForWrite)
384 if (!d_ptr.testAndSetOrdered(expectedValue: d, newValue: nullptr, currentValue&: d))
385 continue;
386 return;
387 }
388
389 if ((quintptr(d) & StateMask) == StateLockedForRead) {
390 Q_ASSERT(quintptr(d) > (1U<<4)); //otherwise that would be the fast case
391 // Just decrease the reader's count.
392 auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) - (1U<<4));
393 if (!d_ptr.testAndSetOrdered(expectedValue: d, newValue: val, currentValue&: d))
394 continue;
395 return;
396 }
397
398 Q_ASSERT(!isUncontendedLocked(d));
399
400 if (d->recursive) {
401 d->recursiveUnlock();
402 return;
403 }
404
405 const auto lock = qt_scoped_lock(mutex&: d->mutex);
406 if (d->writerCount) {
407 Q_ASSERT(d->writerCount == 1);
408 Q_ASSERT(d->readerCount == 0);
409 d->writerCount = 0;
410 } else {
411 Q_ASSERT(d->readerCount > 0);
412 d->readerCount--;
413 if (d->readerCount > 0)
414 return;
415 }
416
417 if (d->waitingReaders || d->waitingWriters) {
418 d->unlock();
419 } else {
420 Q_ASSERT(d_ptr.loadRelaxed() == d); // should not change when we still hold the mutex
421 d_ptr.storeRelease(newValue: nullptr);
422 d->release();
423 }
424 return;
425 }
426}
427
428bool QReadWriteLockPrivate::lockForRead(std::unique_lock<std::mutex> &lock, QDeadlineTimer timeout)
429{
430 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
431
432 while (waitingWriters || writerCount) {
433 if (timeout.hasExpired())
434 return false;
435 if (!timeout.isForever()) {
436 waitingReaders++;
437 readerCond.wait_until(lock&: lock, atime: timeout.deadline<steady_clock>());
438 } else {
439 waitingReaders++;
440 readerCond.wait(lock&: lock);
441 }
442 waitingReaders--;
443 }
444 readerCount++;
445 Q_ASSERT(writerCount == 0);
446 return true;
447}
448
449bool QReadWriteLockPrivate::lockForWrite(std::unique_lock<std::mutex> &lock, QDeadlineTimer timeout)
450{
451 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
452
453 while (readerCount || writerCount) {
454 if (timeout.hasExpired()) {
455 if (waitingReaders && !waitingWriters && !writerCount) {
456 // We timed out and now there is no more writers or waiting writers, but some
457 // readers were queued (probably because of us). Wake the waiting readers.
458 readerCond.notify_all();
459 }
460 return false;
461 }
462 if (!timeout.isForever()) {
463 waitingWriters++;
464 writerCond.wait_until(lock&: lock, atime: timeout.deadline<steady_clock>());
465 } else {
466 waitingWriters++;
467 writerCond.wait(lock&: lock);
468 }
469 waitingWriters--;
470 }
471
472 Q_ASSERT(writerCount == 0);
473 Q_ASSERT(readerCount == 0);
474 writerCount = 1;
475 return true;
476}
477
478void QReadWriteLockPrivate::unlock()
479{
480 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
481 if (waitingWriters)
482 writerCond.notify_one();
483 else if (waitingReaders)
484 readerCond.notify_all();
485}
486
487static auto handleEquals(Qt::HANDLE handle)
488{
489 return [handle](QReadWriteLockPrivate::Reader reader) { return reader.handle == handle; };
490}
491
492bool QReadWriteLockPrivate::recursiveLockForRead(QDeadlineTimer timeout)
493{
494 Q_ASSERT(recursive);
495 auto lock = qt_unique_lock(mutex);
496
497 Qt::HANDLE self = QThread::currentThreadId();
498
499 auto it = std::find_if(first: currentReaders.begin(), last: currentReaders.end(),
500 pred: handleEquals(handle: self));
501 if (it != currentReaders.end()) {
502 ++it->recursionLevel;
503 return true;
504 }
505
506 if (!lockForRead(lock, timeout))
507 return false;
508
509 Reader r = {.handle: self, .recursionLevel: 1};
510 currentReaders.append(t: std::move(r));
511 return true;
512}
513
514bool QReadWriteLockPrivate::recursiveLockForWrite(QDeadlineTimer timeout)
515{
516 Q_ASSERT(recursive);
517 auto lock = qt_unique_lock(mutex);
518
519 Qt::HANDLE self = QThread::currentThreadId();
520 if (currentWriter == self) {
521 writerCount++;
522 return true;
523 }
524
525 if (!lockForWrite(lock, timeout))
526 return false;
527
528 currentWriter = self;
529 return true;
530}
531
532void QReadWriteLockPrivate::recursiveUnlock()
533{
534 Q_ASSERT(recursive);
535 auto lock = qt_unique_lock(mutex);
536
537 Qt::HANDLE self = QThread::currentThreadId();
538 if (self == currentWriter) {
539 if (--writerCount > 0)
540 return;
541 currentWriter = nullptr;
542 } else {
543 auto it = std::find_if(first: currentReaders.begin(), last: currentReaders.end(),
544 pred: handleEquals(handle: self));
545 if (it == currentReaders.end()) {
546 qWarning(msg: "QReadWriteLock::unlock: unlocking from a thread that did not lock");
547 return;
548 } else {
549 if (--it->recursionLevel <= 0) {
550 currentReaders.erase(pos: it);
551 readerCount--;
552 }
553 if (readerCount)
554 return;
555 }
556 }
557
558 unlock();
559}
560
561// The freelist management
562namespace {
563struct QReadWriteLockFreeListConstants : QFreeListDefaultConstants
564{
565 enum { BlockCount = 4, MaxIndex=0xffff };
566 static const int Sizes[BlockCount];
567};
568Q_CONSTINIT const int
569 QReadWriteLockFreeListConstants::Sizes[QReadWriteLockFreeListConstants::BlockCount] = {
570 16, 128, 1024, QReadWriteLockFreeListConstants::MaxIndex - (16 + 128 + 1024)
571 };
572
573typedef QFreeList<QReadWriteLockPrivate, QReadWriteLockFreeListConstants> QReadWriteLockFreeList;
574Q_GLOBAL_STATIC(QReadWriteLockFreeList, qrwl_freelist);
575}
576
577QReadWriteLockPrivate *QReadWriteLockPrivate::allocate()
578{
579 int i = qrwl_freelist->next();
580 QReadWriteLockPrivate *d = &(*qrwl_freelist)[i];
581 d->id = i;
582 Q_ASSERT(!d->recursive);
583 Q_ASSERT(!d->waitingReaders && !d->waitingWriters && !d->readerCount && !d->writerCount);
584 return d;
585}
586
587void QReadWriteLockPrivate::release()
588{
589 Q_ASSERT(!recursive);
590 Q_ASSERT(!waitingReaders && !waitingWriters && !readerCount && !writerCount);
591 qrwl_freelist->release(id);
592}
593
594/*!
595 \class QReadLocker
596 \inmodule QtCore
597 \brief The QReadLocker class is a convenience class that
598 simplifies locking and unlocking read-write locks for read access.
599
600 \threadsafe
601
602 \ingroup thread
603
604 The purpose of QReadLocker (and QWriteLocker) is to simplify
605 QReadWriteLock locking and unlocking. Locking and unlocking
606 statements or in exception handling code is error-prone and
607 difficult to debug. QReadLocker can be used in such situations
608 to ensure that the state of the lock is always well-defined.
609
610 Here's an example that uses QReadLocker to lock and unlock a
611 read-write lock for reading:
612
613 \snippet code/src_corelib_thread_qreadwritelock.cpp 1
614
615 It is equivalent to the following code:
616
617 \snippet code/src_corelib_thread_qreadwritelock.cpp 2
618
619 The QMutexLocker documentation shows examples where the use of a
620 locker object greatly simplifies programming.
621
622 \sa QWriteLocker, QReadWriteLock
623*/
624
625/*!
626 \fn QReadLocker::QReadLocker(QReadWriteLock *lock)
627
628 Constructs a QReadLocker and locks \a lock for reading. The lock
629 will be unlocked when the QReadLocker is destroyed. If \c lock is
630 zero, QReadLocker does nothing.
631
632 \sa QReadWriteLock::lockForRead()
633*/
634
635/*!
636 \fn QReadLocker::~QReadLocker()
637
638 Destroys the QReadLocker and unlocks the lock that was passed to
639 the constructor.
640
641 \sa QReadWriteLock::unlock()
642*/
643
644/*!
645 \fn void QReadLocker::unlock()
646
647 Unlocks the lock associated with this locker.
648
649 \sa QReadWriteLock::unlock()
650*/
651
652/*!
653 \fn void QReadLocker::relock()
654
655 Relocks an unlocked lock.
656
657 \sa unlock()
658*/
659
660/*!
661 \fn QReadWriteLock *QReadLocker::readWriteLock() const
662
663 Returns a pointer to the read-write lock that was passed
664 to the constructor.
665*/
666
667/*!
668 \class QWriteLocker
669 \inmodule QtCore
670 \brief The QWriteLocker class is a convenience class that
671 simplifies locking and unlocking read-write locks for write access.
672
673 \threadsafe
674
675 \ingroup thread
676
677 The purpose of QWriteLocker (and QReadLocker) is to simplify
678 QReadWriteLock locking and unlocking. Locking and unlocking
679 statements or in exception handling code is error-prone and
680 difficult to debug. QWriteLocker can be used in such situations
681 to ensure that the state of the lock is always well-defined.
682
683 Here's an example that uses QWriteLocker to lock and unlock a
684 read-write lock for writing:
685
686 \snippet code/src_corelib_thread_qreadwritelock.cpp 3
687
688 It is equivalent to the following code:
689
690 \snippet code/src_corelib_thread_qreadwritelock.cpp 4
691
692 The QMutexLocker documentation shows examples where the use of a
693 locker object greatly simplifies programming.
694
695 \sa QReadLocker, QReadWriteLock
696*/
697
698/*!
699 \fn QWriteLocker::QWriteLocker(QReadWriteLock *lock)
700
701 Constructs a QWriteLocker and locks \a lock for writing. The lock
702 will be unlocked when the QWriteLocker is destroyed. If \c lock is
703 zero, QWriteLocker does nothing.
704
705 \sa QReadWriteLock::lockForWrite()
706*/
707
708/*!
709 \fn QWriteLocker::~QWriteLocker()
710
711 Destroys the QWriteLocker and unlocks the lock that was passed to
712 the constructor.
713
714 \sa QReadWriteLock::unlock()
715*/
716
717/*!
718 \fn void QWriteLocker::unlock()
719
720 Unlocks the lock associated with this locker.
721
722 \sa QReadWriteLock::unlock()
723*/
724
725/*!
726 \fn void QWriteLocker::relock()
727
728 Relocks an unlocked lock.
729
730 \sa unlock()
731*/
732
733/*!
734 \fn QReadWriteLock *QWriteLocker::readWriteLock() const
735
736 Returns a pointer to the read-write lock that was passed
737 to the constructor.
738*/
739
740QT_END_NAMESPACE
741

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