QReadWriteLock: add tryLockForXxx overloads taking QDeadlineTimer

This propagates inside the internals, ending up in wait_until calls in
the internal std::condition_variable. For systems with proper support
for monotonic waiting (Linux, FreeBSD), this should improve performance.

We could even remove the hasExpired() check and pass a time point in the
past too. Right now, there's a minor performance drawback for
tryLockForXxxx(), because we will make at least two system calls to get
the time.

Change-Id: I6f518d59e63249ddbf43fffd1759fc5b2e40256a
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Thiago Macieira 2023-04-27 20:35:30 -07:00
parent fcae43237b
commit 63704529b7
3 changed files with 98 additions and 48 deletions

View File

@ -31,7 +31,7 @@ QT_BEGIN_NAMESPACE
using namespace QReadWriteLockStates;
namespace {
using ms = std::chrono::milliseconds;
using steady_clock = std::chrono::steady_clock;
const auto dummyLockedForRead = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(StateLockedForRead));
const auto dummyLockedForWrite = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(StateLockedForWrite));
@ -40,9 +40,9 @@ inline bool isUncontendedLocked(const QReadWriteLockPrivate *d)
}
static bool contendedTryLockForRead(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
int timeout, QReadWriteLockPrivate *d);
QDeadlineTimer timeout, QReadWriteLockPrivate *d);
static bool contendedTryLockForWrite(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
int timeout, QReadWriteLockPrivate *d);
QDeadlineTimer timeout, QReadWriteLockPrivate *d);
/*! \class QReadWriteLock
\inmodule QtCore
@ -143,6 +143,7 @@ QReadWriteLock::~QReadWriteLock()
*/
/*!
\fn bool QReadWriteLock::tryLockForRead(int timeout)
Attempts to lock for reading. This function returns \c true if the
lock was obtained; otherwise it returns \c false. If another thread
@ -161,7 +162,25 @@ QReadWriteLock::~QReadWriteLock()
\sa unlock(), lockForRead()
*/
bool QReadWriteLock::tryLockForRead(int timeout)
/*!
\overload
\since 6.6
Attempts to lock for reading. This function returns \c true if the lock was
obtained; otherwise it returns \c false. If another thread has locked for
writing, this function will wait until \a timeout expires for the lock to
become available.
If the lock was obtained, the lock must be unlocked with unlock()
before another thread can successfully lock it for writing.
It is not possible to lock for read if the thread already has
locked for write.
\sa unlock(), lockForRead()
*/
bool QReadWriteLock::tryLockForRead(QDeadlineTimer timeout)
{
// Fast case: non contended:
QReadWriteLockPrivate *d = d_ptr.loadRelaxed();
@ -171,7 +190,7 @@ bool QReadWriteLock::tryLockForRead(int timeout)
}
Q_NEVER_INLINE static bool contendedTryLockForRead(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
int timeout, QReadWriteLockPrivate *d)
QDeadlineTimer timeout, QReadWriteLockPrivate *d)
{
while (true) {
if (d == nullptr) {
@ -191,7 +210,7 @@ Q_NEVER_INLINE static bool contendedTryLockForRead(QAtomicPointer<QReadWriteLock
}
if (d == dummyLockedForWrite) {
if (!timeout)
if (timeout.hasExpired())
return false;
// locked for write, assign a d_ptr and wait.
@ -239,6 +258,8 @@ Q_NEVER_INLINE static bool contendedTryLockForRead(QAtomicPointer<QReadWriteLock
*/
/*!
\fn QReadWriteLock::tryLockForWrite(int timeout)
Attempts to lock for writing. This function returns \c true if the
lock was obtained; otherwise it returns \c false. If another thread
has locked for reading or writing, this function will wait for at
@ -256,7 +277,25 @@ Q_NEVER_INLINE static bool contendedTryLockForRead(QAtomicPointer<QReadWriteLock
\sa unlock(), lockForWrite()
*/
bool QReadWriteLock::tryLockForWrite(int timeout)
/*!
\overload
\since 6.6
Attempts to lock for writing. This function returns \c true if the lock was
obtained; otherwise it returns \c false. If another thread has locked for
reading or writing, this function will wait until \a timeout expires for
the lock to become available.
If the lock was obtained, the lock must be unlocked with unlock()
before another thread can successfully lock it.
It is not possible to lock for write if the thread already has
locked for read.
\sa unlock(), lockForWrite()
*/
bool QReadWriteLock::tryLockForWrite(QDeadlineTimer timeout)
{
// Fast case: non contended:
QReadWriteLockPrivate *d = d_ptr.loadRelaxed();
@ -266,7 +305,7 @@ bool QReadWriteLock::tryLockForWrite(int timeout)
}
Q_NEVER_INLINE static bool contendedTryLockForWrite(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
int timeout, QReadWriteLockPrivate *d)
QDeadlineTimer timeout, QReadWriteLockPrivate *d)
{
while (true) {
if (d == nullptr) {
@ -276,7 +315,7 @@ Q_NEVER_INLINE static bool contendedTryLockForWrite(QAtomicPointer<QReadWriteLoc
}
if (isUncontendedLocked(d)) {
if (!timeout)
if (timeout.hasExpired())
return false;
// locked for either read or write, assign a d_ptr and wait.
@ -370,23 +409,16 @@ void QReadWriteLock::unlock()
}
}
bool QReadWriteLockPrivate::lockForRead(std::unique_lock<QtPrivate::mutex> &lock, int timeout)
bool QReadWriteLockPrivate::lockForRead(std::unique_lock<QtPrivate::mutex> &lock, QDeadlineTimer timeout)
{
Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
QElapsedTimer t;
if (timeout > 0)
t.start();
while (waitingWriters || writerCount) {
if (timeout == 0)
if (timeout.hasExpired())
return false;
if (timeout > 0) {
auto elapsed = t.elapsed();
if (elapsed > timeout)
return false;
if (!timeout.isForever()) {
waitingReaders++;
readerCond.wait_for(lock, ms{timeout - elapsed});
readerCond.wait_until(lock, timeout.deadline<steady_clock>());
} else {
waitingReaders++;
readerCond.wait(lock);
@ -398,29 +430,22 @@ bool QReadWriteLockPrivate::lockForRead(std::unique_lock<QtPrivate::mutex> &lock
return true;
}
bool QReadWriteLockPrivate::lockForWrite(std::unique_lock<QtPrivate::mutex> &lock, int timeout)
bool QReadWriteLockPrivate::lockForWrite(std::unique_lock<QtPrivate::mutex> &lock, QDeadlineTimer timeout)
{
Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
QElapsedTimer t;
if (timeout > 0)
t.start();
while (readerCount || writerCount) {
if (timeout == 0)
return false;
if (timeout > 0) {
auto elapsed = t.elapsed();
if (elapsed > timeout) {
if (waitingReaders && !waitingWriters && !writerCount) {
// We timed out and now there is no more writers or waiting writers, but some
// readers were queued (probably because of us). Wake the waiting readers.
readerCond.notify_all();
}
return false;
if (timeout.hasExpired()) {
if (waitingReaders && !waitingWriters && !writerCount) {
// We timed out and now there is no more writers or waiting writers, but some
// readers were queued (probably because of us). Wake the waiting readers.
readerCond.notify_all();
}
return false;
}
if (!timeout.isForever()) {
waitingWriters++;
writerCond.wait_for(lock, ms{timeout - elapsed});
writerCond.wait_until(lock, timeout.deadline<steady_clock>());
} else {
waitingWriters++;
writerCond.wait(lock);
@ -448,7 +473,7 @@ static auto handleEquals(Qt::HANDLE handle)
return [handle](QReadWriteLockPrivate::Reader reader) { return reader.handle == handle; };
}
bool QReadWriteLockPrivate::recursiveLockForRead(int timeout)
bool QReadWriteLockPrivate::recursiveLockForRead(QDeadlineTimer timeout)
{
Q_ASSERT(recursive);
auto lock = qt_unique_lock(mutex);
@ -470,7 +495,7 @@ bool QReadWriteLockPrivate::recursiveLockForRead(int timeout)
return true;
}
bool QReadWriteLockPrivate::recursiveLockForWrite(int timeout)
bool QReadWriteLockPrivate::recursiveLockForWrite(QDeadlineTimer timeout)
{
Q_ASSERT(recursive);
auto lock = qt_unique_lock(mutex);

View File

@ -5,10 +5,10 @@
#define QREADWRITELOCK_H
#include <QtCore/qglobal.h>
#include <QtCore/qdeadlinetimer.h>
QT_BEGIN_NAMESPACE
#if QT_CONFIG(thread)
class QReadWriteLockPrivate;
@ -26,17 +26,30 @@ public:
#if QT_CORE_REMOVED_SINCE(6, 6)
bool tryLockForRead();
#endif
bool tryLockForRead(int timeout = 0);
QT_CORE_INLINE_SINCE(6, 6)
bool tryLockForRead(int timeout);
bool tryLockForRead(QDeadlineTimer timeout = {});
QT_CORE_INLINE_SINCE(6, 6)
void lockForWrite();
#if QT_CORE_REMOVED_SINCE(6, 6)
bool tryLockForWrite();
#endif
bool tryLockForWrite(int timeout = 0);
QT_CORE_INLINE_SINCE(6, 6)
bool tryLockForWrite(int timeout);
bool tryLockForWrite(QDeadlineTimer timeout = {});
void unlock();
#ifndef Q_QDOC
// because tryLockForXxx(QDeadlineTimer::Forever) is the same
// as tryLockForXxx(0), which is not forever
bool tryLockForRead(QDeadlineTimer::ForeverConstant)
{ lockForRead(); return true; }
bool tryLockForWrite(QDeadlineTimer::ForeverConstant)
{ lockForWrite(); return true; }
#endif
private:
Q_DISABLE_COPY(QReadWriteLock)
QAtomicPointer<QReadWriteLockPrivate> d_ptr;
@ -46,12 +59,22 @@ private:
#if QT_CORE_INLINE_IMPL_SINCE(6, 6)
void QReadWriteLock::lockForRead()
{
tryLockForRead(-1);
tryLockForRead(QDeadlineTimer(QDeadlineTimer::Forever));
}
bool QReadWriteLock::tryLockForRead(int timeout)
{
return tryLockForRead(QDeadlineTimer(timeout));
}
void QReadWriteLock::lockForWrite()
{
tryLockForWrite(-1);
tryLockForWrite(QDeadlineTimer(QDeadlineTimer::Forever));
}
bool QReadWriteLock::tryLockForWrite(int timeout)
{
return tryLockForWrite(QDeadlineTimer(timeout));
}
#endif // inline since 6.6
@ -164,10 +187,12 @@ public:
void lockForRead() noexcept { }
bool tryLockForRead() noexcept { return true; }
bool tryLockForRead(QDeadlineTimer) noexcept { return true; }
bool tryLockForRead(int timeout) noexcept { Q_UNUSED(timeout); return true; }
void lockForWrite() noexcept { }
bool tryLockForWrite() noexcept { return true; }
bool tryLockForWrite(QDeadlineTimer) noexcept { return true; }
bool tryLockForWrite(int timeout) noexcept { Q_UNUSED(timeout); return true; }
void unlock() noexcept { }

View File

@ -55,8 +55,8 @@ public:
const bool recursive;
//Called with the mutex locked
bool lockForWrite(std::unique_lock<QtPrivate::mutex> &lock, int timeout);
bool lockForRead(std::unique_lock<QtPrivate::mutex> &lock, int timeout);
bool lockForWrite(std::unique_lock<QtPrivate::mutex> &lock, QDeadlineTimer timeout);
bool lockForRead(std::unique_lock<QtPrivate::mutex> &lock, QDeadlineTimer timeout);
void unlock();
//memory management
@ -75,8 +75,8 @@ public:
QVarLengthArray<Reader, 16> currentReaders;
// called with the mutex unlocked
bool recursiveLockForWrite(int timeout);
bool recursiveLockForRead(int timeout);
bool recursiveLockForWrite(QDeadlineTimer timeout);
bool recursiveLockForRead(QDeadlineTimer timeout);
void recursiveUnlock();
static QReadWriteLockStates::StateForWaitCondition