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