diff options
Diffstat (limited to 'src/corelib/thread/qsemaphore.cpp')
-rw-r--r-- | src/corelib/thread/qsemaphore.cpp | 206 |
1 files changed, 113 insertions, 93 deletions
diff --git a/src/corelib/thread/qsemaphore.cpp b/src/corelib/thread/qsemaphore.cpp index 0f1c7c2eb8..72781942ac 100644 --- a/src/corelib/thread/qsemaphore.cpp +++ b/src/corelib/thread/qsemaphore.cpp @@ -1,49 +1,16 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Copyright (C) 2018 Intel Corporation. -** 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) 2022 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qsemaphore.h" -#include "qmutex.h" #include "qfutex_p.h" -#include "qwaitcondition.h" #include "qdeadlinetimer.h" #include "qdatetime.h" +#include "qdebug.h" +#include "qlocking_p.h" +#include "qwaitcondition_p.h" + +#include <chrono> QT_BEGIN_NAMESPACE @@ -83,7 +50,7 @@ using namespace QtFutex; A typical application of semaphores is for controlling access to a circular buffer shared by a producer thread and a consumer - thread. The \l{Semaphores Example} shows how + thread. The \l{Producer and Consumer using Semaphores} example shows how to use QSemaphore to solve that problem. A non-computing example of a semaphore would be dining at a @@ -96,7 +63,8 @@ using namespace QtFutex; seated (taking the available seats to 5, making the party of 10 people wait longer). - \sa QSemaphoreReleaser, QMutex, QWaitCondition, QThread, {Semaphores Example} + \sa QSemaphoreReleaser, QMutex, QWaitCondition, QThread, + {Producer and Consumer using Semaphores} */ /* @@ -178,10 +146,10 @@ static QBasicAtomicInteger<quint32> *futexHigh32(QBasicAtomicInteger<quintptr> * } template <bool IsTimed> bool -futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValue, quintptr nn, int timeout) +futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValue, quintptr nn, + QDeadlineTimer timer) { - QDeadlineTimer timer(IsTimed ? QDeadlineTimer(timeout) : QDeadlineTimer()); - qint64 remainingTime = timeout * Q_INT64_C(1000) * 1000; + using namespace std::chrono; int n = int(unsigned(nn)); // we're called after one testAndSet, so start by waiting first @@ -194,12 +162,12 @@ futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValu if constexpr (futexHasWaiterCount) { Q_ASSERT(n > 1); ptr = futexHigh32(&u); - curValue >>= 32; + curValue = quint64(curValue) >> 32; } } - if (IsTimed && remainingTime > 0) { - bool timedout = !futexWait(*ptr, curValue, remainingTime); + if (IsTimed) { + bool timedout = !futexWait(*ptr, curValue, timer); if (timedout) return false; } else { @@ -207,8 +175,6 @@ futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValu } curValue = u.loadAcquire(); - if (IsTimed) - remainingTime = timer.remainingTimeNSecs(); // try to acquire while (futexAvailCounter(curValue) >= n) { @@ -218,13 +184,18 @@ futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValu } // not enough tokens available, put us to wait - if (remainingTime == 0) + if (IsTimed && timer.hasExpired()) return false; } } -template <bool IsTimed> bool futexSemaphoreTryAcquire(QBasicAtomicInteger<quintptr> &u, int n, int timeout) +static constexpr QDeadlineTimer::ForeverConstant Expired = + QDeadlineTimer::ForeverConstant(1); + +template <typename T> bool +futexSemaphoreTryAcquire(QBasicAtomicInteger<quintptr> &u, int n, T timeout) { + constexpr bool IsTimed = std::is_same_v<QDeadlineTimer, T>; // Try to acquire without waiting (we still loop because the testAndSet // call can fail). quintptr nn = unsigned(n); @@ -238,15 +209,21 @@ template <bool IsTimed> bool futexSemaphoreTryAcquire(QBasicAtomicInteger<quintp if (u.testAndSetOrdered(curValue, newValue, curValue)) return true; // succeeded! } - if (timeout == 0) - return false; + if constexpr (IsTimed) { + if (timeout.hasExpired()) + return false; + } else { + if (timeout == Expired) + return false; + } // we need to wait constexpr quintptr oneWaiter = quintptr(Q_UINT64_C(1) << 32); // zero on 32-bit if constexpr (futexHasWaiterCount) { // We don't use the fetched value from above so futexWait() fails if // it changed after the testAndSetOrdered above. - if (((curValue >> 32) & 0x7fffffffU) == 0x7fffffffU) { + quint32 waiterCount = (quint64(curValue) >> 32) & 0x7fffffffU; + if (waiterCount == 0x7fffffffU) { qCritical() << "Waiter count overflow in QSemaphore"; return false; } @@ -272,14 +249,31 @@ template <bool IsTimed> bool futexSemaphoreTryAcquire(QBasicAtomicInteger<quintp return false; } -class QSemaphorePrivate { -public: - inline QSemaphorePrivate(int n) : avail(n) { } +namespace QtSemaphorePrivate { +using namespace QtPrivate; +struct Layout1 +{ + alignas(IdealMutexAlignment) std::mutex mutex; + qsizetype avail = 0; + alignas(IdealMutexAlignment) std::condition_variable cond; +}; - QMutex mutex; - QWaitCondition cond; +struct Layout2 +{ + alignas(IdealMutexAlignment) std::mutex mutex; + alignas(IdealMutexAlignment) std::condition_variable cond; + qsizetype avail = 0; +}; + +// Choose Layout1 if it is smaller than Layout2. That happens for platforms +// where sizeof(mutex) is 64. +using Members = std::conditional_t<sizeof(Layout1) <= sizeof(Layout2), Layout1, Layout2>; +} // namespace QtSemaphorePrivate - int avail; +class QSemaphorePrivate : public QtSemaphorePrivate::Members +{ +public: + explicit QSemaphorePrivate(qsizetype n) { avail = n; } }; /*! @@ -322,16 +316,22 @@ QSemaphore::~QSemaphore() */ void QSemaphore::acquire(int n) { +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) +# warning "Move the Q_ASSERT to inline code, make QSemaphore have wide contract, " \ + "and mark noexcept where futexes are in use." +#else Q_ASSERT_X(n >= 0, "QSemaphore::acquire", "parameter 'n' must be non-negative"); +#endif if (futexAvailable()) { - futexSemaphoreTryAcquire<false>(u, n, -1); + futexSemaphoreTryAcquire(u, n, QDeadlineTimer::Forever); return; } - QMutexLocker locker(&d->mutex); - while (n > d->avail) - d->cond.wait(locker.mutex()); + const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; }; + + auto locker = qt_unique_lock(d->mutex); + d->cond.wait(locker, sufficientResourcesAvailable); d->avail -= n; } @@ -356,7 +356,12 @@ void QSemaphore::release(int n) quintptr nn = unsigned(n); if (futexHasWaiterCount) nn |= quint64(nn) << 32; // token count replicated in high word - quintptr prevValue = u.fetchAndAddRelease(nn); + quintptr prevValue = u.loadRelaxed(); + quintptr newValue; + do { // loop just to ensure the operations are done atomically + newValue = prevValue + nn; + newValue &= (futexNeedsWakeAllBit - 1); + } while (!u.testAndSetRelease(prevValue, newValue, prevValue)); if (futexNeedsWake(prevValue)) { #ifdef FUTEX_OP if (futexHasWaiterCount) { @@ -378,7 +383,6 @@ void QSemaphore::release(int n) quint32 oparg = 0; quint32 cmp = FUTEX_OP_CMP_NE; quint32 cmparg = 0; - u.fetchAndAndRelease(futexNeedsWakeAllBit - 1); futexWakeOp(*futexLow32(&u), n, INT_MAX, *futexHigh32(&u), FUTEX_OP(op, oparg, cmp, cmparg)); return; } @@ -390,20 +394,18 @@ void QSemaphore::release(int n) // its acquisition anyway, so it has to wait; // 2) it did not see the new counter value, in which case its // futexWait will fail. - u.fetchAndAndRelease(futexNeedsWakeAllBit - 1); - if (futexHasWaiterCount) { - futexWakeAll(*futexLow32(&u)); + futexWakeAll(*futexLow32(&u)); + if (futexHasWaiterCount) futexWakeAll(*futexHigh32(&u)); - } else { - futexWakeAll(u); - } } return; } - QMutexLocker locker(&d->mutex); + // Keep mutex locked until after notify_all() lest another thread acquire()s + // the semaphore once d->avail == 0 and then destroys it, leaving `d` dangling. + const auto locker = qt_scoped_lock(d->mutex); d->avail += n; - d->cond.wakeAll(); + d->cond.notify_all(); } /*! @@ -417,7 +419,7 @@ int QSemaphore::available() const if (futexAvailable()) return futexAvailCounter(u.loadRelaxed()); - QMutexLocker locker(&d->mutex); + const auto locker = qt_scoped_lock(d->mutex); return d->avail; } @@ -437,9 +439,9 @@ bool QSemaphore::tryAcquire(int n) Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative"); if (futexAvailable()) - return futexSemaphoreTryAcquire<false>(u, n, 0); + return futexSemaphoreTryAcquire(u, n, Expired); - QMutexLocker locker(&d->mutex); + const auto locker = qt_scoped_lock(d->mutex); if (n > d->avail) return false; d->avail -= n; @@ -447,6 +449,8 @@ bool QSemaphore::tryAcquire(int n) } /*! + \fn QSemaphore::tryAcquire(int n, int timeout) + Tries to acquire \c n resources guarded by the semaphore and returns \c true on success. If available() < \a n, this call will wait for at most \a timeout milliseconds for resources to become @@ -462,24 +466,40 @@ bool QSemaphore::tryAcquire(int n) \sa acquire() */ -bool QSemaphore::tryAcquire(int n, int timeout) + +/*! + \since 6.6 + + Tries to acquire \c n resources guarded by the semaphore and returns \c + true on success. If available() < \a n, this call will wait until \a timer + expires for resources to become available. + + Example: + + \snippet code/src_corelib_thread_qsemaphore.cpp tryAcquire-QDeadlineTimer + + \sa acquire() +*/ +bool QSemaphore::tryAcquire(int n, QDeadlineTimer timer) { - Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative"); + if (timer.isForever()) { + acquire(n); + return true; + } - // We're documented to accept any negative value as "forever" - // but QDeadlineTimer only accepts -1. - timeout = qMax(timeout, -1); + if (timer.hasExpired()) + return tryAcquire(n); + + Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative"); if (futexAvailable()) - return futexSemaphoreTryAcquire<true>(u, n, timeout); + return futexSemaphoreTryAcquire(u, n, timer); - QDeadlineTimer timer(timeout); - QMutexLocker locker(&d->mutex); - while (n > d->avail && !timer.hasExpired()) { - if (!d->cond.wait(locker.mutex(), timer)) - return false; - } - if (n > d->avail) + using namespace std::chrono; + const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; }; + + auto locker = qt_unique_lock(d->mutex); + if (!d->cond.wait_until(locker, timer.deadline<steady_clock>(), sufficientResourcesAvailable)) return false; d->avail -= n; return true; @@ -511,7 +531,7 @@ bool QSemaphore::tryAcquire(int n, int timeout) It is equivalent to calling \c{tryAcquire(1, timeout)}, where the call times out on the given \a timeout value. The function returns \c true - on accquiring the resource successfully. + on acquiring the resource successfully. \sa tryAcquire(), try_acquire(), try_acquire_until() */ |