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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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