diff options
Diffstat (limited to 'src/corelib/thread/qmutex.cpp')
-rw-r--r-- | src/corelib/thread/qmutex.cpp | 296 |
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 |