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