Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qsemaphore.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// Copyright (C) 2018 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qsemaphore.h"
6#include "qfutex_p.h"
7#include "qdeadlinetimer.h"
8#include "qdatetime.h"
9#include "qdebug.h"
10#include "qlocking_p.h"
11#include "qwaitcondition_p.h"
12
13#include <chrono>
14
16
17using namespace QtFutex;
18
69/*
70 QSemaphore futex operation
71
72 QSemaphore stores a 32-bit integer with the counter of currently available
73 tokens (value between 0 and INT_MAX). When a thread attempts to acquire n
74 tokens and the counter is larger than that, we perform a compare-and-swap
75 with the new count. If that succeeds, the acquisition worked; if not, we
76 loop again because the counter changed. If there were not enough tokens,
77 we'll perform a futex-wait.
78
79 Before we do, we set the high bit in the futex to indicate that semaphore
80 is contended: that is, there's a thread waiting for more tokens. On
81 release() for n tokens, we perform a fetch-and-add of n and then check if
82 that high bit was set. If it was, then we clear that bit and perform a
83 futex-wake on the semaphore to indicate the waiting threads can wake up and
84 acquire tokens. Which ones get woken up is unspecified.
85
86 If the system has the ability to wake up a precise number of threads, has
87 Linux's FUTEX_WAKE_OP functionality, and is 64-bit, instead of using a
88 single bit indicating a contended semaphore, we'll store the number of
89 tokens *plus* total number of waiters in the high word. Additionally, all
90 multi-token waiters will be waiting on that high word. So when releasing n
91 tokens on those systems, we tell the kernel to wake up n single-token
92 threads and all of the multi-token ones. Which threads get woken up is
93 unspecified, but it's likely single-token threads will get woken up first.
94 */
95
96#if defined(FUTEX_OP) && QT_POINTER_SIZE > 4
97static constexpr bool futexHasWaiterCount = true;
98#else
99static constexpr bool futexHasWaiterCount = false;
100#endif
101
103 (Q_UINT64_C(1) << (sizeof(quintptr) * CHAR_BIT - 1)) : 0x80000000U;
104
106{
107 // the low 31 bits
109 // the high bit of the low word isn't used
110 Q_ASSERT((v & 0x80000000U) == 0);
111
112 // so we can be a little faster
113 return int(unsigned(v));
114 }
115 return int(v & 0x7fffffffU);
116}
117
119{
120 // If we're counting waiters, the number of waiters plus value is stored in the
121 // low 31 bits of the high word (that is, bits 32-62). If we're not, then we only
122 // use futexNeedsWakeAllBit to indicate anyone is waiting.
123 if constexpr (futexHasWaiterCount)
124 return unsigned(quint64(v) >> 32) > unsigned(v);
125 return v >> 31;
126}
127
129{
130 auto result = reinterpret_cast<QBasicAtomicInteger<quint32> *>(ptr);
131#if Q_BYTE_ORDER == Q_BIG_ENDIAN && QT_POINTER_SIZE > 4
132 ++result;
133#endif
134 return result;
135}
136
138{
140 auto result = reinterpret_cast<QBasicAtomicInteger<quint32> *>(ptr);
141#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN && QT_POINTER_SIZE > 4
142 ++result;
143#endif
144 return result;
145}
146
147template <bool IsTimed> bool
150{
151 using namespace std::chrono;
152 int n = int(unsigned(nn));
153
154 // we're called after one testAndSet, so start by waiting first
155 for (;;) {
156 // indicate we're waiting
157 auto ptr = futexLow32(&u);
158 if (n > 1 || !futexHasWaiterCount) {
160 curValue |= futexNeedsWakeAllBit;
161 if constexpr (futexHasWaiterCount) {
162 Q_ASSERT(n > 1);
163 ptr = futexHigh32(&u);
164 curValue = quint64(curValue) >> 32;
165 }
166 }
167
168 if (IsTimed) {
169 bool timedout = !futexWait(*ptr, curValue, timer);
170 if (timedout)
171 return false;
172 } else {
173 futexWait(*ptr, curValue);
174 }
175
176 curValue = u.loadAcquire();
177
178 // try to acquire
179 while (futexAvailCounter(curValue) >= n) {
180 quintptr newValue = curValue - nn;
181 if (u.testAndSetOrdered(curValue, newValue, curValue))
182 return true; // succeeded!
183 }
184
185 // not enough tokens available, put us to wait
186 if (IsTimed && timer.hasExpired())
187 return false;
188 }
189}
190
193
194template <typename T> bool
196{
197 constexpr bool IsTimed = std::is_same_v<QDeadlineTimer, T>;
198 // Try to acquire without waiting (we still loop because the testAndSet
199 // call can fail).
200 quintptr nn = unsigned(n);
202 nn |= quint64(nn) << 32; // token count replicated in high word
203
204 quintptr curValue = u.loadAcquire();
205 while (futexAvailCounter(curValue) >= n) {
206 // try to acquire
207 quintptr newValue = curValue - nn;
208 if (u.testAndSetOrdered(curValue, newValue, curValue))
209 return true; // succeeded!
210 }
211 if constexpr (IsTimed) {
212 if (timeout.hasExpired())
213 return false;
214 } else {
215 if (timeout == Expired)
216 return false;
217 }
218
219 // we need to wait
220 constexpr quintptr oneWaiter = quintptr(Q_UINT64_C(1) << 32); // zero on 32-bit
221 if constexpr (futexHasWaiterCount) {
222 // We don't use the fetched value from above so futexWait() fails if
223 // it changed after the testAndSetOrdered above.
224 quint32 waiterCount = (quint64(curValue) >> 32) & 0x7fffffffU;
225 if (waiterCount == 0x7fffffffU) {
226 qCritical() << "Waiter count overflow in QSemaphore";
227 return false;
228 }
229
230 // increase the waiter count
231 u.fetchAndAddRelaxed(oneWaiter);
232 curValue += oneWaiter;
233
234 // Also adjust nn to subtract oneWaiter when we succeed in acquiring.
235 nn += oneWaiter;
236 }
237
238 if (futexSemaphoreTryAcquire_loop<IsTimed>(u, curValue, nn, timeout))
239 return true;
240
241 Q_ASSERT(IsTimed);
242
244 // decrement the number of threads waiting
245 Q_ASSERT(futexHigh32(&u)->loadRelaxed() & 0x7fffffffU);
246 u.fetchAndSubRelaxed(oneWaiter);
247 }
248 return false;
249}
250
252using namespace QtPrivate;
254{
255 alignas(IdealMutexAlignment) std::mutex mutex;
257 alignas(IdealMutexAlignment) std::condition_variable cond;
258};
259
261{
262 alignas(IdealMutexAlignment) std::mutex mutex;
263 alignas(IdealMutexAlignment) std::condition_variable cond;
265};
266
267// Choose Layout1 if it is smaller than Layout2. That happens for platforms
268// where sizeof(mutex) is 64.
269using Members = std::conditional_t<sizeof(Layout1) <= sizeof(Layout2), Layout1, Layout2>;
270} // namespace QtSemaphorePrivate
271
273{
274public:
275 explicit QSemaphorePrivate(qsizetype n) { avail = n; }
276};
277
285{
286 Q_ASSERT_X(n >= 0, "QSemaphore", "parameter 'n' must be non-negative");
287 if (futexAvailable()) {
288 quintptr nn = unsigned(n);
290 nn |= quint64(nn) << 32; // token count replicated in high word
291 u.storeRelaxed(nn);
292 } else {
293 d = new QSemaphorePrivate(n);
294 }
295}
296
304{
305 if (!futexAvailable())
306 delete d;
307}
308
317{
318#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
319# warning "Move the Q_ASSERT to inline code, make QSemaphore have wide contract, " \
320 "and mark noexcept where futexes are in use."
321#else
322 Q_ASSERT_X(n >= 0, "QSemaphore::acquire", "parameter 'n' must be non-negative");
323#endif
324
325 if (futexAvailable()) {
327 return;
328 }
329
330 const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; };
331
332 auto locker = qt_unique_lock(d->mutex);
333 d->cond.wait(locker, sufficientResourcesAvailable);
334 d->avail -= n;
335}
336
351{
352 Q_ASSERT_X(n >= 0, "QSemaphore::release", "parameter 'n' must be non-negative");
353
354 if (futexAvailable()) {
355 quintptr nn = unsigned(n);
357 nn |= quint64(nn) << 32; // token count replicated in high word
358 quintptr prevValue = u.loadRelaxed();
359 quintptr newValue;
360 do { // loop just to ensure the operations are done atomically
361 newValue = prevValue + nn;
362 newValue &= (futexNeedsWakeAllBit - 1);
363 } while (!u.testAndSetRelease(prevValue, newValue, prevValue));
364 if (futexNeedsWake(prevValue)) {
365#ifdef FUTEX_OP
367 /*
368 On 64-bit systems, the single-token waiters wait on the low half
369 and the multi-token waiters wait on the upper half. So we ask
370 the kernel to wake up n single-token waiters and all multi-token
371 waiters (if any), and clear the multi-token wait bit.
372
373 atomic {
374 int oldval = *upper;
375 *upper = oldval | 0;
376 futexWake(lower, n);
377 if (oldval != 0) // always true
378 futexWake(upper, INT_MAX);
379 }
380 */
381 quint32 op = FUTEX_OP_OR;
382 quint32 oparg = 0;
383 quint32 cmp = FUTEX_OP_CMP_NE;
384 quint32 cmparg = 0;
385 futexWakeOp(*futexLow32(&u), n, INT_MAX, *futexHigh32(&u), FUTEX_OP(op, oparg, cmp, cmparg));
386 return;
387 }
388#endif
389 // Unset the bit and wake everyone. There are two possibilities
390 // under which a thread can set the bit between the AND and the
391 // futexWake:
392 // 1) it did see the new counter value, but it wasn't enough for
393 // its acquisition anyway, so it has to wait;
394 // 2) it did not see the new counter value, in which case its
395 // futexWait will fail.
399 }
400 return;
401 }
402
403 const auto locker = qt_scoped_lock(d->mutex);
404 d->avail += n;
405 d->cond.notify_all();
406}
407
415{
416 if (futexAvailable())
418
419 const auto locker = qt_scoped_lock(d->mutex);
420 return d->avail;
421}
422
435{
436 Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
437
438 if (futexAvailable())
440
441 const auto locker = qt_scoped_lock(d->mutex);
442 if (n > d->avail)
443 return false;
444 d->avail -= n;
445 return true;
446}
447
481{
482 if (timer.isForever()) {
483 acquire(n);
484 return true;
485 }
486
487 if (timer.hasExpired())
488 return tryAcquire(n);
489
490 Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
491
492 if (futexAvailable())
494
495 using namespace std::chrono;
496 const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; };
497
498 auto locker = qt_unique_lock(d->mutex);
499 if (!d->cond.wait_until(locker, timer.deadline<steady_clock>(), sufficientResourcesAvailable))
500 return false;
501 d->avail -= n;
502 return true;
503}
504
T fetchAndOrRelaxed(T valueToAdd) noexcept
bool testAndSetOrdered(T expectedValue, T newValue) noexcept
void storeRelaxed(T newValue) noexcept
T loadAcquire() const noexcept
T fetchAndSubRelaxed(T valueToAdd) noexcept
T fetchAndAddRelaxed(T valueToAdd) noexcept
bool testAndSetRelease(T expectedValue, T newValue) noexcept
T loadRelaxed() const noexcept
\inmodule QtCore
ForeverConstant
\value Forever Used when creating a QDeadlineTimer to indicate the deadline should not expire
static constexpr ForeverConstant Forever
QSemaphorePrivate(qsizetype n)
QSemaphorePrivate * d
Definition qsemaphore.h:50
~QSemaphore()
Destroys the semaphore.
void acquire(int n=1)
Tries to acquire n resources guarded by the semaphore.
QBasicAtomicInteger< quintptr > u
Definition qsemaphore.h:51
bool tryAcquire(int n=1)
Tries to acquire n resources guarded by the semaphore and returns true on success.
void release(int n=1)
Releases n resources guarded by the semaphore.
QSemaphore(int n=0)
Creates a new semaphore and initializes the number of resources it guards to n (by default,...
int available() const
Returns the number of resources currently available to the semaphore.
Combined button and popup list for selecting options.
void futexWakeAll(Atomic &futex)
void futexWait(Atomic &futex, typename Atomic::Type expectedValue)
constexpr bool futexAvailable()
\macro QT_NAMESPACE
static constexpr quintptr IdealMutexAlignment
std::conditional_t< sizeof(Layout1)<=sizeof(Layout2), Layout1, Layout2 > Members
#define qCritical
Definition qlogging.h:163
static ControlElement< T > * ptr(QWidget *widget)
GLsizei const GLfloat * v
[13]
GLbitfield GLuint64 timeout
[4]
GLfloat n
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
static QBasicAtomicInteger< quint32 > * futexHigh32(QBasicAtomicInteger< quintptr > *ptr)
static QBasicAtomicInteger< quint32 > * futexLow32(QBasicAtomicInteger< quintptr > *ptr)
static constexpr bool futexHasWaiterCount
bool futexSemaphoreTryAcquire_loop(QBasicAtomicInteger< quintptr > &u, quintptr curValue, quintptr nn, QDeadlineTimer timer)
static bool futexNeedsWake(quintptr v)
static constexpr quintptr futexNeedsWakeAllBit
static int futexAvailCounter(quintptr v)
static constexpr QDeadlineTimer::ForeverConstant Expired
bool futexSemaphoreTryAcquire(QBasicAtomicInteger< quintptr > &u, int n, T timeout)
#define Q_UINT64_C(c)
Definition qtypes.h:53
unsigned int quint32
Definition qtypes.h:45
size_t quintptr
Definition qtypes.h:72
unsigned long long quint64
Definition qtypes.h:56
ptrdiff_t qsizetype
Definition qtypes.h:70
QTimer * timer
[3]
sem acquire()
std::condition_variable cond
std::condition_variable cond