Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
70/*
71 QSemaphore futex operation
72
73 QSemaphore stores a 32-bit integer with the counter of currently available
74 tokens (value between 0 and INT_MAX). When a thread attempts to acquire n
75 tokens and the counter is larger than that, we perform a compare-and-swap
76 with the new count. If that succeeds, the acquisition worked; if not, we
77 loop again because the counter changed. If there were not enough tokens,
78 we'll perform a futex-wait.
79
80 Before we do, we set the high bit in the futex to indicate that semaphore
81 is contended: that is, there's a thread waiting for more tokens. On
82 release() for n tokens, we perform a fetch-and-add of n and then check if
83 that high bit was set. If it was, then we clear that bit and perform a
84 futex-wake on the semaphore to indicate the waiting threads can wake up and
85 acquire tokens. Which ones get woken up is unspecified.
86
87 If the system has the ability to wake up a precise number of threads, has
88 Linux's FUTEX_WAKE_OP functionality, and is 64-bit, instead of using a
89 single bit indicating a contended semaphore, we'll store the number of
90 tokens *plus* total number of waiters in the high word. Additionally, all
91 multi-token waiters will be waiting on that high word. So when releasing n
92 tokens on those systems, we tell the kernel to wake up n single-token
93 threads and all of the multi-token ones. Which threads get woken up is
94 unspecified, but it's likely single-token threads will get woken up first.
95 */
96
97#if defined(FUTEX_OP) && QT_POINTER_SIZE > 4
98static constexpr bool futexHasWaiterCount = true;
99#else
100static constexpr bool futexHasWaiterCount = false;
101#endif
102
104 (Q_UINT64_C(1) << (sizeof(quintptr) * CHAR_BIT - 1)) : 0x80000000U;
105
107{
108 // the low 31 bits
110 // the high bit of the low word isn't used
111 Q_ASSERT((v & 0x80000000U) == 0);
112
113 // so we can be a little faster
114 return int(unsigned(v));
115 }
116 return int(v & 0x7fffffffU);
117}
118
120{
121 // If we're counting waiters, the number of waiters plus value is stored in the
122 // low 31 bits of the high word (that is, bits 32-62). If we're not, then we only
123 // use futexNeedsWakeAllBit to indicate anyone is waiting.
124 if constexpr (futexHasWaiterCount)
125 return unsigned(quint64(v) >> 32) > unsigned(v);
126 return v >> 31;
127}
128
129static QBasicAtomicInteger<quint32> *futexLow32(QBasicAtomicInteger<quintptr> *ptr)
130{
131 auto result = reinterpret_cast<QBasicAtomicInteger<quint32> *>(ptr);
132#if Q_BYTE_ORDER == Q_BIG_ENDIAN && QT_POINTER_SIZE > 4
133 ++result;
134#endif
135 return result;
136}
137
138static QBasicAtomicInteger<quint32> *futexHigh32(QBasicAtomicInteger<quintptr> *ptr)
139{
141 auto result = reinterpret_cast<QBasicAtomicInteger<quint32> *>(ptr);
142#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN && QT_POINTER_SIZE > 4
143 ++result;
144#endif
145 return result;
146}
147
148template <bool IsTimed> bool
149futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValue, quintptr nn,
151{
152 using namespace std::chrono;
153 int n = int(unsigned(nn));
154
155 // we're called after one testAndSet, so start by waiting first
156 for (;;) {
157 // indicate we're waiting
158 auto ptr = futexLow32(&u);
159 if (n > 1 || !futexHasWaiterCount) {
160 u.fetchAndOrRelaxed(futexNeedsWakeAllBit);
161 curValue |= futexNeedsWakeAllBit;
162 if constexpr (futexHasWaiterCount) {
163 Q_ASSERT(n > 1);
164 ptr = futexHigh32(&u);
165 curValue = quint64(curValue) >> 32;
166 }
167 }
168
169 if (IsTimed) {
170 bool timedout = !futexWait(*ptr, curValue, timer);
171 if (timedout)
172 return false;
173 } else {
174 futexWait(*ptr, curValue);
175 }
176
177 curValue = u.loadAcquire();
178
179 // try to acquire
180 while (futexAvailCounter(curValue) >= n) {
181 quintptr newValue = curValue - nn;
182 if (u.testAndSetOrdered(curValue, newValue, curValue))
183 return true; // succeeded!
184 }
185
186 // not enough tokens available, put us to wait
187 if (IsTimed && timer.hasExpired())
188 return false;
189 }
190}
191
194
195template <typename T> bool
196futexSemaphoreTryAcquire(QBasicAtomicInteger<quintptr> &u, int n, T timeout)
197{
198 constexpr bool IsTimed = std::is_same_v<QDeadlineTimer, T>;
199 // Try to acquire without waiting (we still loop because the testAndSet
200 // call can fail).
201 quintptr nn = unsigned(n);
203 nn |= quint64(nn) << 32; // token count replicated in high word
204
205 quintptr curValue = u.loadAcquire();
206 while (futexAvailCounter(curValue) >= n) {
207 // try to acquire
208 quintptr newValue = curValue - nn;
209 if (u.testAndSetOrdered(curValue, newValue, curValue))
210 return true; // succeeded!
211 }
212 if constexpr (IsTimed) {
213 if (timeout.hasExpired())
214 return false;
215 } else {
216 if (timeout == Expired)
217 return false;
218 }
219
220 // we need to wait
221 constexpr quintptr oneWaiter = quintptr(Q_UINT64_C(1) << 32); // zero on 32-bit
222 if constexpr (futexHasWaiterCount) {
223 // We don't use the fetched value from above so futexWait() fails if
224 // it changed after the testAndSetOrdered above.
225 quint32 waiterCount = (quint64(curValue) >> 32) & 0x7fffffffU;
226 if (waiterCount == 0x7fffffffU) {
227 qCritical() << "Waiter count overflow in QSemaphore";
228 return false;
229 }
230
231 // increase the waiter count
232 u.fetchAndAddRelaxed(oneWaiter);
233 curValue += oneWaiter;
234
235 // Also adjust nn to subtract oneWaiter when we succeed in acquiring.
236 nn += oneWaiter;
237 }
238
239 if (futexSemaphoreTryAcquire_loop<IsTimed>(u, curValue, nn, timeout))
240 return true;
241
242 Q_ASSERT(IsTimed);
243
245 // decrement the number of threads waiting
246 Q_ASSERT(futexHigh32(&u)->loadRelaxed() & 0x7fffffffU);
247 u.fetchAndSubRelaxed(oneWaiter);
248 }
249 return false;
250}
251
253using namespace QtPrivate;
255{
256 alignas(IdealMutexAlignment) std::mutex mutex;
258 alignas(IdealMutexAlignment) std::condition_variable cond;
259};
260
262{
263 alignas(IdealMutexAlignment) std::mutex mutex;
264 alignas(IdealMutexAlignment) std::condition_variable cond;
266};
267
268// Choose Layout1 if it is smaller than Layout2. That happens for platforms
269// where sizeof(mutex) is 64.
270using Members = std::conditional_t<sizeof(Layout1) <= sizeof(Layout2), Layout1, Layout2>;
271} // namespace QtSemaphorePrivate
272
274{
275public:
276 explicit QSemaphorePrivate(qsizetype n) { avail = n; }
277};
278
286{
287 Q_ASSERT_X(n >= 0, "QSemaphore", "parameter 'n' must be non-negative");
288 if (futexAvailable()) {
289 quintptr nn = unsigned(n);
291 nn |= quint64(nn) << 32; // token count replicated in high word
292 u.storeRelaxed(nn);
293 } else {
294 d = new QSemaphorePrivate(n);
295 }
296}
297
305{
306 if (!futexAvailable())
307 delete d;
308}
309
318{
319#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
320# warning "Move the Q_ASSERT to inline code, make QSemaphore have wide contract, " \
321 "and mark noexcept where futexes are in use."
322#else
323 Q_ASSERT_X(n >= 0, "QSemaphore::acquire", "parameter 'n' must be non-negative");
324#endif
325
326 if (futexAvailable()) {
328 return;
329 }
330
331 const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; };
332
333 auto locker = qt_unique_lock(d->mutex);
334 d->cond.wait(locker, sufficientResourcesAvailable);
335 d->avail -= n;
336}
337
352{
353 Q_ASSERT_X(n >= 0, "QSemaphore::release", "parameter 'n' must be non-negative");
354
355 if (futexAvailable()) {
356 quintptr nn = unsigned(n);
358 nn |= quint64(nn) << 32; // token count replicated in high word
359 quintptr prevValue = u.loadRelaxed();
360 quintptr newValue;
361 do { // loop just to ensure the operations are done atomically
362 newValue = prevValue + nn;
363 newValue &= (futexNeedsWakeAllBit - 1);
364 } while (!u.testAndSetRelease(prevValue, newValue, prevValue));
365 if (futexNeedsWake(prevValue)) {
366#ifdef FUTEX_OP
368 /*
369 On 64-bit systems, the single-token waiters wait on the low half
370 and the multi-token waiters wait on the upper half. So we ask
371 the kernel to wake up n single-token waiters and all multi-token
372 waiters (if any), and clear the multi-token wait bit.
373
374 atomic {
375 int oldval = *upper;
376 *upper = oldval | 0;
377 futexWake(lower, n);
378 if (oldval != 0) // always true
379 futexWake(upper, INT_MAX);
380 }
381 */
382 quint32 op = FUTEX_OP_OR;
383 quint32 oparg = 0;
384 quint32 cmp = FUTEX_OP_CMP_NE;
385 quint32 cmparg = 0;
386 futexWakeOp(*futexLow32(&u), n, INT_MAX, *futexHigh32(&u), FUTEX_OP(op, oparg, cmp, cmparg));
387 return;
388 }
389#endif
390 // Unset the bit and wake everyone. There are two possibilities
391 // under which a thread can set the bit between the AND and the
392 // futexWake:
393 // 1) it did see the new counter value, but it wasn't enough for
394 // its acquisition anyway, so it has to wait;
395 // 2) it did not see the new counter value, in which case its
396 // futexWait will fail.
400 }
401 return;
402 }
403
404 // Keep mutex locked until after notify_all() lest another thread acquire()s
405 // the semaphore once d->avail == 0 and then destroys it, leaving `d` dangling.
406 const auto locker = qt_scoped_lock(d->mutex);
407 d->avail += n;
408 d->cond.notify_all();
409}
410
418{
419 if (futexAvailable())
421
422 const auto locker = qt_scoped_lock(d->mutex);
423 return d->avail;
424}
425
438{
439 Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
440
441 if (futexAvailable())
443
444 const auto locker = qt_scoped_lock(d->mutex);
445 if (n > d->avail)
446 return false;
447 d->avail -= n;
448 return true;
449}
450
484{
485 if (timer.isForever()) {
486 acquire(n);
487 return true;
488 }
489
490 if (timer.hasExpired())
491 return tryAcquire(n);
492
493 Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
494
495 if (futexAvailable())
497
498 using namespace std::chrono;
499 const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; };
500
501 auto locker = qt_unique_lock(d->mutex);
502 if (!d->cond.wait_until(locker, timer.deadline<steady_clock>(), sufficientResourcesAvailable))
503 return false;
504 d->avail -= n;
505 return true;
506}
507
void storeRelaxed(T newValue) 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:52
~QSemaphore()
Destroys the semaphore.
void acquire(int n=1)
Tries to acquire n resources guarded by the semaphore.
QBasicAtomicInteger< quintptr > u
Definition qsemaphore.h:53
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_NO_KEYWORDS >
static constexpr quintptr IdealMutexAlignment
std::conditional_t< sizeof(Layout1)<=sizeof(Layout2), Layout1, Layout2 > Members
#define qCritical
Definition qlogging.h:167
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:58
unsigned int quint32
Definition qtypes.h:50
size_t quintptr
Definition qtypes.h:167
unsigned long long quint64
Definition qtypes.h:61
ptrdiff_t qsizetype
Definition qtypes.h:165
QTimer * timer
[3]
sem acquire()
std::condition_variable cond
std::condition_variable cond