summaryrefslogtreecommitdiffstats
path: root/src/corelib/thread/qmutex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/thread/qmutex.cpp')
-rw-r--r--src/corelib/thread/qmutex.cpp296
1 files changed, 232 insertions, 64 deletions
diff --git a/src/corelib/thread/qmutex.cpp b/src/corelib/thread/qmutex.cpp
index 4926eae2e9..ec6c711a4f 100644
--- a/src/corelib/thread/qmutex.cpp
+++ b/src/corelib/thread/qmutex.cpp
@@ -1,58 +1,29 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2016 Intel Corporation.
-** Copyright (C) 2012 Olivier Goffart <ogoffart@woboq.com>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtCore module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// Copyright (C) 2012 Olivier Goffart <ogoffart@woboq.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "global/qglobal.h"
#include "qplatformdefs.h"
#include "qmutex.h"
#include <qdebug.h>
#include "qatomic.h"
-#include "qelapsedtimer.h"
+#include "qfutex_p.h"
#include "qthread.h"
#include "qmutex_p.h"
-#ifndef QT_LINUX_FUTEX
+#ifndef QT_ALWAYS_USE_FUTEX
#include "private/qfreelist_p.h"
#endif
QT_BEGIN_NAMESPACE
+using namespace QtFutex;
+static inline QMutexPrivate *dummyFutexValue()
+{
+ return reinterpret_cast<QMutexPrivate *>(quintptr(3));
+}
+
/*
\class QBasicMutex
\inmodule QtCore
@@ -134,12 +105,12 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d)
{
if (!d)
return;
-#ifndef QT_LINUX_FUTEX
- if (d != dummyLocked() && d->possiblyUnlocked.loadRelaxed() && tryLock()) {
- unlock();
- return;
+ if (!futexAvailable()) {
+ if (d != dummyLocked() && d->possiblyUnlocked.loadRelaxed() && tryLock()) {
+ unlock();
+ return;
+ }
}
-#endif
qWarning("QMutex: destroying locked mutex");
}
@@ -174,6 +145,23 @@ void QBasicMutex::destroyInternal(QMutexPrivate *d)
\sa lock(), unlock()
*/
+/*! \fn bool QMutex::tryLock(QDeadlineTimer timer)
+ \since 6.6
+
+ Attempts to lock the mutex. This function returns \c true if the lock
+ was obtained; otherwise it returns \c false. If another thread has
+ locked the mutex, this function will wait until \a timer expires
+ for the mutex to become available.
+
+ If the lock was obtained, the mutex must be unlocked with unlock()
+ before another thread can successfully lock it.
+
+ Calling this function multiple times on the same mutex from the
+ same thread will cause a \e dead-lock.
+
+ \sa lock(), unlock()
+*/
+
/*! \fn bool QMutex::tryLock()
\overload
@@ -307,6 +295,8 @@ QRecursiveMutex::~QRecursiveMutex()
*/
/*!
+ \fn QRecursiveMutex::tryLock(int timeout)
+
Attempts to lock the mutex. This function returns \c true if the lock
was obtained; otherwise it returns \c false. If another thread has
locked the mutex, this function will wait for at most \a timeout
@@ -324,16 +314,37 @@ QRecursiveMutex::~QRecursiveMutex()
\sa lock(), unlock()
*/
-bool QRecursiveMutex::tryLock(int timeout) QT_MUTEX_LOCK_NOEXCEPT
+
+/*!
+ \since 6.6
+
+ Attempts to lock the mutex. This function returns \c true if the lock
+ was obtained; otherwise it returns \c false. If another thread has
+ locked the mutex, this function will wait until \a timeout expires
+ for the mutex to become available.
+
+ If the lock was obtained, the mutex must be unlocked with unlock()
+ before another thread can successfully lock it.
+
+ Calling this function multiple times on the same mutex from the
+ same thread is allowed.
+
+ \sa lock(), unlock()
+*/
+bool QRecursiveMutex::tryLock(QDeadlineTimer timeout) QT_MUTEX_LOCK_NOEXCEPT
{
+ unsigned tsanFlags = QtTsan::MutexWriteReentrant | QtTsan::TryLock;
+ QtTsan::mutexPreLock(this, tsanFlags);
+
Qt::HANDLE self = QThread::currentThreadId();
if (owner.loadRelaxed() == self) {
++count;
Q_ASSERT_X(count != 0, "QMutex::lock", "Overflow in recursion counter");
+ QtTsan::mutexPostLock(this, tsanFlags, 0);
return true;
}
bool success = true;
- if (timeout == -1) {
+ if (timeout.isForever()) {
mutex.lock();
} else {
success = mutex.tryLock(timeout);
@@ -341,6 +352,11 @@ bool QRecursiveMutex::tryLock(int timeout) QT_MUTEX_LOCK_NOEXCEPT
if (success)
owner.storeRelaxed(self);
+ else
+ tsanFlags |= QtTsan::TryLockFailed;
+
+ QtTsan::mutexPostLock(this, tsanFlags, 0);
+
return success;
}
@@ -404,6 +420,7 @@ bool QRecursiveMutex::tryLock(int timeout) QT_MUTEX_LOCK_NOEXCEPT
void QRecursiveMutex::unlock() noexcept
{
Q_ASSERT(owner.loadRelaxed() == QThread::currentThreadId());
+ QtTsan::mutexPreUnlock(this, 0u);
if (count > 0) {
count--;
@@ -411,6 +428,8 @@ void QRecursiveMutex::unlock() noexcept
owner.storeRelaxed(nullptr);
mutex.unlock();
}
+
+ QtTsan::mutexPostUnlock(this, 0u);
}
@@ -478,6 +497,40 @@ void QRecursiveMutex::unlock() noexcept
*/
/*!
+ \fn template <typename Mutex> QMutexLocker<Mutex>::QMutexLocker(QMutexLocker &&other) noexcept
+ \since 6.4
+
+ Move-constructs a QMutexLocker from \a other. The mutex and the
+ state of \a other is transferred to the newly constructed instance.
+ After the move, \a other will no longer be managing any mutex.
+
+ \sa QMutex::lock()
+*/
+
+/*!
+ \fn template <typename Mutex> QMutexLocker<Mutex> &QMutexLocker<Mutex>::operator=(QMutexLocker &&other) noexcept
+ \since 6.4
+
+ Move-assigns \a other onto this QMutexLocker. If this QMutexLocker
+ was holding a locked mutex before the assignment, the mutex will be
+ unlocked. The mutex and the state of \a other is then transferred
+ to this QMutexLocker. After the move, \a other will no longer be
+ managing any mutex.
+
+ \sa QMutex::lock()
+*/
+
+/*!
+ \fn template <typename Mutex> void QMutexLocker<Mutex>::swap(QMutexLocker &other) noexcept
+ \since 6.4
+
+ Swaps the mutex and the state of this QMutexLocker with \a other.
+ This operation is very fast and never fails.
+
+ \sa QMutex::lock()
+*/
+
+/*!
\fn template <typename Mutex> QMutexLocker<Mutex>::~QMutexLocker() noexcept
Destroys the QMutexLocker and unlocks the mutex that was locked
@@ -487,6 +540,14 @@ void QRecursiveMutex::unlock() noexcept
*/
/*!
+ \fn template <typename Mutex> bool QMutexLocker<Mutex>::isLocked() const noexcept
+ \since 6.4
+
+ Returns true if this QMutexLocker is currently locking its associated
+ mutex, or false otherwise.
+*/
+
+/*!
\fn template <typename Mutex> void QMutexLocker<Mutex>::unlock() noexcept
Unlocks this mutex locker. You can use \c relock() to lock
@@ -510,8 +571,6 @@ void QRecursiveMutex::unlock() noexcept
*/
-#ifndef QT_LINUX_FUTEX //linux implementation is in qmutex_linux.cpp
-
/*
For a rough introduction on how this works, refer to
http://woboq.com/blog/internals-of-qmutex-in-qt5.html
@@ -532,26 +591,125 @@ void QRecursiveMutex::unlock() noexcept
possiblyUnlocked flag.
*/
+/*
+ * QBasicMutex implementation with futexes (Linux, Windows 10)
+ *
+ * QBasicMutex contains one pointer value, which can contain one of four
+ * different values:
+ * 0x0 unlocked
+ * 0x1 locked, no waiters
+ * 0x3 locked, at least one waiter
+ *
+ * LOCKING:
+ *
+ * A starts in the 0x0 state, indicating that it's unlocked. When the first
+ * thread attempts to lock it, it will perform a testAndSetAcquire
+ * from 0x0 to 0x1. If that succeeds, the caller concludes that it
+ * successfully locked the mutex. That happens in fastTryLock().
+ *
+ * If that testAndSetAcquire fails, QBasicMutex::lockInternal is called.
+ *
+ * lockInternal will examine the value of the pointer. Otherwise, it will use
+ * futexes to sleep and wait for another thread to unlock. To do that, it needs
+ * to set a pointer value of 0x3, which indicates that thread is waiting. It
+ * does that by a simple fetchAndStoreAcquire operation.
+ *
+ * If the pointer value was 0x0, it means we succeeded in acquiring the mutex.
+ * For other values, it will then call FUTEX_WAIT and with an expected value of
+ * 0x3.
+ *
+ * If the pointer value changed before futex(2) managed to sleep, it will
+ * return -1 / EWOULDBLOCK, in which case we have to start over. And even if we
+ * are woken up directly by a FUTEX_WAKE, we need to acquire the mutex, so we
+ * start over again.
+ *
+ * UNLOCKING:
+ *
+ * To unlock, we need to set a value of 0x0 to indicate it's unlocked. The
+ * first attempt is a testAndSetRelease operation from 0x1 to 0x0. If that
+ * succeeds, we're done.
+ *
+ * If it fails, unlockInternal() is called. The only possibility is that the
+ * mutex value was 0x3, which indicates some other thread is waiting or was
+ * waiting in the past. We then set the mutex to 0x0 and perform a FUTEX_WAKE.
+ */
+
/*!
\internal helper for lock()
*/
void QBasicMutex::lockInternal() QT_MUTEX_LOCK_NOEXCEPT
{
- lockInternal(-1);
+ if (futexAvailable()) {
+ // note we must set to dummyFutexValue because there could be other threads
+ // also waiting
+ while (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) != nullptr) {
+ // successfully set the waiting bit, now sleep
+ futexWait(d_ptr, dummyFutexValue());
+
+ // we got woken up, so try to acquire the mutex
+ }
+ Q_ASSERT(d_ptr.loadRelaxed());
+ } else {
+ lockInternal(-1);
+ }
}
/*!
\internal helper for lock(int)
*/
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT
{
+ if (timeout == 0)
+ return false;
+
+ return lockInternal(QDeadlineTimer(timeout));
+}
+#endif
+
+/*!
+ \internal helper for tryLock(QDeadlineTimer)
+ */
+bool QBasicMutex::lockInternal(QDeadlineTimer deadlineTimer) QT_MUTEX_LOCK_NOEXCEPT
+{
+ if (deadlineTimer.hasExpired())
+ return false;
+
+ if (futexAvailable()) {
+ if (Q_UNLIKELY(deadlineTimer.isForever())) {
+ lockInternal();
+ return true;
+ }
+
+ // The mutex is already locked, set a bit indicating we're waiting.
+ // Note we must set to dummyFutexValue because there could be other threads
+ // also waiting.
+ if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr)
+ return true;
+
+ for (;;) {
+ if (!futexWait(d_ptr, dummyFutexValue(), deadlineTimer))
+ return false;
+
+ // We got woken up, so must try to acquire the mutex. We must set
+ // to dummyFutexValue() again because there could be other threads
+ // waiting.
+ if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr)
+ return true;
+
+ if (deadlineTimer.hasExpired())
+ return false;
+ }
+ }
+
+#if !defined(QT_ALWAYS_USE_FUTEX)
while (!fastTryLock()) {
QMutexPrivate *copy = d_ptr.loadAcquire();
if (!copy) // if d is 0, the mutex is unlocked
continue;
if (copy == dummyLocked()) {
- if (timeout == 0)
+ if (deadlineTimer.hasExpired())
return false;
// The mutex is locked but does not have a QMutexPrivate yet.
// we need to allocate a QMutexPrivate
@@ -566,7 +724,7 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT
}
QMutexPrivate *d = static_cast<QMutexPrivate *>(copy);
- if (timeout == 0 && !d->possiblyUnlocked.loadRelaxed())
+ if (deadlineTimer.hasExpired() && !d->possiblyUnlocked.loadRelaxed())
return false;
// At this point we have a pointer to a QMutexPrivate. But the other thread
@@ -619,7 +777,7 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT
continue;
}
- if (d->wait(timeout)) {
+ if (d->wait(deadlineTimer)) {
// reset the possiblyUnlocked flag if needed (and deref its corresponding reference)
if (d->possiblyUnlocked.loadRelaxed() && d->possiblyUnlocked.testAndSetRelaxed(true, false))
d->deref();
@@ -628,8 +786,7 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT
Q_ASSERT(d == d_ptr.loadRelaxed());
return true;
} else {
- Q_ASSERT(timeout >= 0);
- //timeout
+ // timed out
d->derefWaiters(1);
//There may be a race in which the mutex is unlocked right after we timed out,
// and before we deref the waiters, so maybe the mutex is actually unlocked.
@@ -644,6 +801,9 @@ bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT
}
Q_ASSERT(d_ptr.loadRelaxed() != 0);
return true;
+#else
+ Q_UNREACHABLE();
+#endif
}
/*!
@@ -655,6 +815,12 @@ void QBasicMutex::unlockInternal() noexcept
Q_ASSERT(copy); //we must be locked
Q_ASSERT(copy != dummyLocked()); // testAndSetRelease(dummyLocked(), 0) failed
+ if (futexAvailable()) {
+ d_ptr.storeRelease(nullptr);
+ return futexWakeOne(d_ptr);
+ }
+
+#if !defined(QT_ALWAYS_USE_FUTEX)
QMutexPrivate *d = reinterpret_cast<QMutexPrivate *>(copy);
// If no one is waiting for the lock anymore, we should reset d to 0x0.
@@ -676,15 +842,19 @@ void QBasicMutex::unlockInternal() noexcept
d->wakeUp();
}
d->deref();
+#else
+ Q_UNUSED(copy);
+#endif
}
+#if !defined(QT_ALWAYS_USE_FUTEX)
//The freelist management
namespace {
struct FreeListConstants : QFreeListDefaultConstants {
enum { BlockCount = 4, MaxIndex=0xffff };
static const int Sizes[BlockCount];
};
-const int FreeListConstants::Sizes[FreeListConstants::BlockCount] = {
+Q_CONSTINIT const int FreeListConstants::Sizes[FreeListConstants::BlockCount] = {
16,
128,
1024,
@@ -693,7 +863,7 @@ const int FreeListConstants::Sizes[FreeListConstants::BlockCount] = {
typedef QFreeList<QMutexPrivate, FreeListConstants> FreeList;
// We cannot use Q_GLOBAL_STATIC because it uses QMutex
-static FreeList freeList_;
+Q_CONSTINIT static FreeList freeList_;
FreeList *freelist()
{
return &freeList_;
@@ -738,12 +908,10 @@ void QMutexPrivate::derefWaiters(int value) noexcept
QT_END_NAMESPACE
-#ifdef QT_LINUX_FUTEX
-# include "qmutex_linux.cpp"
-#elif defined(Q_OS_MAC)
+#if defined(QT_ALWAYS_USE_FUTEX)
+// nothing
+#elif defined(Q_OS_DARWIN)
# include "qmutex_mac.cpp"
-#elif defined(Q_OS_WIN)
-# include "qmutex_win.cpp"
#else
# include "qmutex_unix.cpp"
#endif