diff options
Diffstat (limited to 'src/corelib/thread/qsemaphore.cpp')
-rw-r--r-- | src/corelib/thread/qsemaphore.cpp | 113 |
1 files changed, 80 insertions, 33 deletions
diff --git a/src/corelib/thread/qsemaphore.cpp b/src/corelib/thread/qsemaphore.cpp index 664085eb2b..72781942ac 100644 --- a/src/corelib/thread/qsemaphore.cpp +++ b/src/corelib/thread/qsemaphore.cpp @@ -50,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 @@ -63,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} */ /* @@ -145,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 @@ -161,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 { @@ -174,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) { @@ -185,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); @@ -205,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; } @@ -239,14 +249,31 @@ template <bool IsTimed> bool futexSemaphoreTryAcquire(QBasicAtomicInteger<quintp return false; } -class QSemaphorePrivate { -public: - explicit 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; +}; - QtPrivate::mutex mutex; - QtPrivate::condition_variable cond; +struct Layout2 +{ + alignas(IdealMutexAlignment) std::mutex mutex; + alignas(IdealMutexAlignment) std::condition_variable cond; + qsizetype avail = 0; +}; - int avail; +// 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 + +class QSemaphorePrivate : public QtSemaphorePrivate::Members +{ +public: + explicit QSemaphorePrivate(qsizetype n) { avail = n; } }; /*! @@ -289,10 +316,15 @@ 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; } @@ -362,16 +394,15 @@ 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. - if (futexHasWaiterCount) { - futexWakeAll(*futexLow32(&u)); + futexWakeAll(*futexLow32(&u)); + if (futexHasWaiterCount) futexWakeAll(*futexHigh32(&u)); - } else { - futexWakeAll(u); - } } return; } + // 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.notify_all(); @@ -408,7 +439,7 @@ 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); const auto locker = qt_scoped_lock(d->mutex); if (n > d->avail) @@ -418,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 @@ -433,26 +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) { - if (timeout < 0) { + if (timer.isForever()) { acquire(n); return true; } - if (timeout == 0) + 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); using namespace std::chrono; const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; }; auto locker = qt_unique_lock(d->mutex); - if (!d->cond.wait_for(locker, milliseconds{timeout}, sufficientResourcesAvailable)) + if (!d->cond.wait_until(locker, timer.deadline<steady_clock>(), sufficientResourcesAvailable)) return false; d->avail -= n; return true; |