Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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// Qt-Security score:significant reason:default
5
6#include "qsemaphore.h"
7#include "qfutex_p.h"
9#include "qdatetime.h"
10#include "qdebug.h"
11#include "qlocking_p.h"
13
14#include <chrono>
15
17
18using namespace QtFutex;
19
20#if QT_CONFIG(thread)
21
22/*!
23 \class QSemaphore
24 \inmodule QtCore
25 \brief The QSemaphore class provides a general counting semaphore.
26
27 \threadsafe
28
29 \ingroup thread
30
31 A semaphore is a generalization of a mutex. While a mutex can
32 only be locked once, it's possible to acquire a semaphore
33 multiple times. Semaphores are typically used to protect a
34 certain number of identical resources.
35
36 Semaphores support two fundamental operations, acquire() and
37 release():
38
39 \list
40 \li acquire(\e{n}) tries to acquire \e n resources. If there aren't
41 that many resources available, the call will block until this
42 is the case.
43 \li release(\e{n}) releases \e n resources.
44 \endlist
45
46 There's also a tryAcquire() function that returns immediately if
47 it cannot acquire the resources, and an available() function that
48 returns the number of available resources at any time.
49
50 Example:
51
52 \snippet code/src_corelib_thread_qsemaphore.cpp 0
53
54 A typical application of semaphores is for controlling access to
55 a circular buffer shared by a producer thread and a consumer
56 thread. The \l{Producer and Consumer using Semaphores} example shows how
57 to use QSemaphore to solve that problem.
58
59 A non-computing example of a semaphore would be dining at a
60 restaurant. A semaphore is initialized with the number of chairs
61 in the restaurant. As people arrive, they want a seat. As seats
62 are filled, available() is decremented. As people leave, the
63 available() is incremented, allowing more people to enter. If a
64 party of 10 people want to be seated, but there are only 9 seats,
65 those 10 people will wait, but a party of 4 people would be
66 seated (taking the available seats to 5, making the party of 10
67 people wait longer).
68
69 \sa QSemaphoreReleaser, QMutex, QWaitCondition, QThread,
70 {Producer and Consumer using Semaphores}
71*/
72
73/*
74 QSemaphore futex operation
75
76 QSemaphore stores a 32-bit integer with the counter of currently available
77 tokens (value between 0 and INT_MAX). When a thread attempts to acquire n
78 tokens and the counter is larger than that, we perform a compare-and-swap
79 with the new count. If that succeeds, the acquisition worked; if not, we
80 loop again because the counter changed. If there were not enough tokens,
81 we'll perform a futex-wait.
82
83 Before we do, we set the high bit in the futex to indicate that semaphore
84 is contended: that is, there's a thread waiting for more tokens. On
85 release() for n tokens, we perform a fetch-and-add of n and then check if
86 that high bit was set. If it was, then we clear that bit and perform a
87 futex-wake on the semaphore to indicate the waiting threads can wake up and
88 acquire tokens. Which ones get woken up is unspecified.
89
90 If the system has the ability to wake up a precise number of threads, has
91 Linux's FUTEX_WAKE_OP functionality, and is 64-bit, instead of using a
92 single bit indicating a contended semaphore, we'll store the number of
93 tokens *plus* total number of waiters in the high word. Additionally, all
94 multi-token waiters will be waiting on that high word. So when releasing n
95 tokens on those systems, we tell the kernel to wake up n single-token
96 threads and all of the multi-token ones. Which threads get woken up is
97 unspecified, but it's likely single-token threads will get woken up first.
98 */
99
100#if defined(FUTEX_OP) && QT_POINTER_SIZE > 4
101static constexpr bool futexHasWaiterCount = true;
102#else
103static constexpr bool futexHasWaiterCount = false;
104#endif
105
106static constexpr quintptr futexNeedsWakeAllBit = futexHasWaiterCount ?
107 (Q_UINT64_C(1) << (sizeof(quintptr) * CHAR_BIT - 1)) : 0x80000000U;
108
109static int futexAvailCounter(quintptr v)
110{
111 // the low 31 bits
112 if (futexHasWaiterCount) {
113 // the high bit of the low word isn't used
114 Q_ASSERT((v & 0x80000000U) == 0);
115
116 // so we can be a little faster
117 return int(unsigned(v));
118 }
119 return int(v & 0x7fffffffU);
120}
121
122static bool futexNeedsWake(quintptr v)
123{
124 // If we're counting waiters, the number of waiters plus value is stored in the
125 // low 31 bits of the high word (that is, bits 32-62). If we're not, then we only
126 // use futexNeedsWakeAllBit to indicate anyone is waiting.
127 if constexpr (futexHasWaiterCount)
128 return unsigned(quint64(v) >> 32) > unsigned(v);
129 return v >> 31;
130}
131
132static QBasicAtomicInteger<quint32> *futexLow32(QBasicAtomicInteger<quintptr> *ptr)
133{
134 auto result = reinterpret_cast<QBasicAtomicInteger<quint32> *>(ptr);
135#if Q_BYTE_ORDER == Q_BIG_ENDIAN && QT_POINTER_SIZE > 4
136 ++result;
137#endif
138 return result;
139}
140
141static QBasicAtomicInteger<quint32> *futexHigh32(QBasicAtomicInteger<quintptr> *ptr)
142{
143 Q_ASSERT(futexHasWaiterCount);
144 auto result = reinterpret_cast<QBasicAtomicInteger<quint32> *>(ptr);
145#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN && QT_POINTER_SIZE > 4
146 ++result;
147#endif
148 return result;
149}
150
151template <bool IsTimed> bool
152futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValue, quintptr nn,
153 QDeadlineTimer timer)
154{
155 using namespace std::chrono;
156 int n = int(unsigned(nn));
157
158 // we're called after one testAndSet, so start by waiting first
159 for (;;) {
160 // indicate we're waiting
161 auto ptr = futexLow32(&u);
162 if (n > 1 || !futexHasWaiterCount) {
163 u.fetchAndOrRelaxed(futexNeedsWakeAllBit);
164 curValue |= futexNeedsWakeAllBit;
165 if constexpr (futexHasWaiterCount) {
166 Q_ASSERT(n > 1);
167 ptr = futexHigh32(&u);
168 curValue = quint64(curValue) >> 32;
169 }
170 }
171
172 if (IsTimed) {
173 bool timedout = !futexWait(*ptr, curValue, timer);
174 if (timedout)
175 return false;
176 } else {
177 futexWait(*ptr, curValue);
178 }
179
180 curValue = u.loadAcquire();
181
182 // try to acquire
183 while (futexAvailCounter(curValue) >= n) {
184 quintptr newValue = curValue - nn;
185 if (u.testAndSetOrdered(curValue, newValue, curValue))
186 return true; // succeeded!
187 }
188
189 // not enough tokens available, put us to wait
190 if (IsTimed && timer.hasExpired())
191 return false;
192 }
193}
194
195static constexpr QDeadlineTimer::ForeverConstant Expired =
196 QDeadlineTimer::ForeverConstant(1);
197
198template <typename T> bool
199futexSemaphoreTryAcquire(QBasicAtomicInteger<quintptr> &u, int n, T timeout)
200{
201 constexpr bool IsTimed = std::is_same_v<QDeadlineTimer, T>;
202 // Try to acquire without waiting (we still loop because the testAndSet
203 // call can fail).
204 quintptr nn = unsigned(n);
205 if (futexHasWaiterCount)
206 nn |= quint64(nn) << 32; // token count replicated in high word
207
208 quintptr curValue = u.loadAcquire();
209 while (futexAvailCounter(curValue) >= n) {
210 // try to acquire
211 quintptr newValue = curValue - nn;
212 if (u.testAndSetOrdered(curValue, newValue, curValue))
213 return true; // succeeded!
214 }
215 if constexpr (IsTimed) {
216 if (timeout.hasExpired())
217 return false;
218 } else {
219 if (timeout == Expired)
220 return false;
221 }
222
223 // we need to wait
224 constexpr quintptr oneWaiter = quintptr(Q_UINT64_C(1) << 32); // zero on 32-bit
225 if constexpr (futexHasWaiterCount) {
226 // We don't use the fetched value from above so futexWait() fails if
227 // it changed after the testAndSetOrdered above.
228 quint32 waiterCount = (quint64(curValue) >> 32) & 0x7fffffffU;
229 if (waiterCount == 0x7fffffffU) {
230 qCritical() << "Waiter count overflow in QSemaphore";
231 return false;
232 }
233
234 // increase the waiter count
235 u.fetchAndAddRelaxed(oneWaiter);
236 curValue += oneWaiter;
237
238 // Also adjust nn to subtract oneWaiter when we succeed in acquiring.
239 nn += oneWaiter;
240 }
241
242 if (futexSemaphoreTryAcquire_loop<IsTimed>(u, curValue, nn, timeout))
243 return true;
244
245 Q_ASSERT(IsTimed);
246
247 if (futexHasWaiterCount) {
248 // decrement the number of threads waiting
249 Q_ASSERT(futexHigh32(&u)->loadRelaxed() & 0x7fffffffU);
250 u.fetchAndSubRelaxed(oneWaiter);
251 }
252 return false;
253}
254
255namespace QtSemaphorePrivate {
256using namespace QtPrivate;
257struct Layout1
258{
259 alignas(IdealMutexAlignment) std::mutex mutex;
260 qsizetype avail = 0;
261 alignas(IdealMutexAlignment) std::condition_variable cond;
262};
263
264struct Layout2
265{
266 alignas(IdealMutexAlignment) std::mutex mutex;
267 alignas(IdealMutexAlignment) std::condition_variable cond;
268 qsizetype avail = 0;
269};
270
271// Choose Layout1 if it is smaller than Layout2. That happens for platforms
272// where sizeof(mutex) is 64.
273using Members = std::conditional_t<sizeof(Layout1) <= sizeof(Layout2), Layout1, Layout2>;
274} // namespace QtSemaphorePrivate
275
276class QSemaphorePrivate : public QtSemaphorePrivate::Members
277{
278public:
279 explicit QSemaphorePrivate(qsizetype n) { avail = n; }
280};
281
282/*!
283 Creates a new semaphore and initializes the number of resources
284 it guards to \a n (by default, 0).
285
286 \sa release(), available()
287*/
288QSemaphore::QSemaphore(int n)
289{
290 Q_ASSERT_X(n >= 0, "QSemaphore", "parameter 'n' must be non-negative");
291 if (futexAvailable()) {
292 quintptr nn = unsigned(n);
293 if (futexHasWaiterCount)
294 nn |= quint64(nn) << 32; // token count replicated in high word
295 u.storeRelaxed(nn);
296 } else {
297 d = new QSemaphorePrivate(n);
298 }
299}
300
301/*!
302 Destroys the semaphore.
303
304 \warning Destroying a semaphore that is in use may result in
305 undefined behavior.
306*/
307QSemaphore::~QSemaphore()
308{
309 if (!futexAvailable())
310 delete d;
311}
312
313/*!
314 Tries to acquire \c n resources guarded by the semaphore. If \a n
315 > available(), this call will block until enough resources are
316 available.
317
318 \sa release(), available(), tryAcquire()
319*/
320void QSemaphore::acquire(int n)
321{
322#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
323# warning "Move the Q_ASSERT to inline code, make QSemaphore have wide contract, "
324 "and mark noexcept where futexes are in use."
325#else
326 Q_ASSERT_X(n >= 0, "QSemaphore::acquire", "parameter 'n' must be non-negative");
327#endif
328
329 if (futexAvailable()) {
330 futexSemaphoreTryAcquire(u, n, QDeadlineTimer::Forever);
331 return;
332 }
333
334 const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; };
335
336 auto locker = qt_unique_lock(d->mutex);
337 d->cond.wait(locker, sufficientResourcesAvailable);
338 d->avail -= n;
339}
340
341/*!
342 Releases \a n resources guarded by the semaphore.
343
344 This function can be used to "create" resources as well. For
345 example:
346
347 \snippet code/src_corelib_thread_qsemaphore.cpp 1
348
349 QSemaphoreReleaser is a \l{http://en.cppreference.com/w/cpp/language/raii}{RAII}
350 wrapper around this function.
351
352 \sa acquire(), available(), QSemaphoreReleaser
353*/
354void QSemaphore::release(int n)
355{
356 Q_ASSERT_X(n >= 0, "QSemaphore::release", "parameter 'n' must be non-negative");
357
358 if (futexAvailable()) {
359 quintptr nn = unsigned(n);
360 if (futexHasWaiterCount)
361 nn |= quint64(nn) << 32; // token count replicated in high word
362 quintptr prevValue = u.loadRelaxed();
363 quintptr newValue;
364 do { // loop just to ensure the operations are done atomically
365 newValue = prevValue + nn;
366 newValue &= (futexNeedsWakeAllBit - 1);
367 } while (!u.testAndSetRelease(prevValue, newValue, prevValue));
368 if (futexNeedsWake(prevValue)) {
369#ifdef FUTEX_OP
370 if (futexHasWaiterCount) {
371 /*
372 On 64-bit systems, the single-token waiters wait on the low half
373 and the multi-token waiters wait on the upper half. So we ask
374 the kernel to wake up n single-token waiters and all multi-token
375 waiters (if any), and clear the multi-token wait bit.
376
377 atomic {
378 int oldval = *upper;
379 *upper = oldval | 0;
380 futexWake(lower, n);
381 if (oldval != 0) // always true
382 futexWake(upper, INT_MAX);
383 }
384 */
385 quint32 op = FUTEX_OP_OR;
386 quint32 oparg = 0;
387 quint32 cmp = FUTEX_OP_CMP_NE;
388 quint32 cmparg = 0;
389 futexWakeOp(*futexLow32(&u), n, INT_MAX, *futexHigh32(&u), FUTEX_OP(op, oparg, cmp, cmparg));
390 return;
391 }
392#endif
393 // Unset the bit and wake everyone. There are two possibilities
394 // under which a thread can set the bit between the AND and the
395 // futexWake:
396 // 1) it did see the new counter value, but it wasn't enough for
397 // its acquisition anyway, so it has to wait;
398 // 2) it did not see the new counter value, in which case its
399 // futexWait will fail.
400 futexWakeAll(*futexLow32(&u));
401 if (futexHasWaiterCount)
402 futexWakeAll(*futexHigh32(&u));
403 }
404 return;
405 }
406
407 // Keep mutex locked until after notify_all() lest another thread acquire()s
408 // the semaphore once d->avail == 0 and then destroys it, leaving `d` dangling.
409 const auto locker = qt_scoped_lock(d->mutex);
410 d->avail += n;
411 d->cond.notify_all();
412}
413
414/*!
415 Returns the number of resources currently available to the
416 semaphore. This number can never be negative.
417
418 \sa acquire(), release()
419*/
420int QSemaphore::available() const
421{
422 if (futexAvailable())
423 return futexAvailCounter(u.loadRelaxed());
424
425 const auto locker = qt_scoped_lock(d->mutex);
426 return d->avail;
427}
428
429/*!
430 Tries to acquire \c n resources guarded by the semaphore and
431 returns \c true on success. If available() < \a n, this call
432 immediately returns \c false without acquiring any resources.
433
434 Example:
435
436 \snippet code/src_corelib_thread_qsemaphore.cpp 2
437
438 \sa acquire()
439*/
440bool QSemaphore::tryAcquire(int n)
441{
442 Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
443
444 if (futexAvailable())
445 return futexSemaphoreTryAcquire(u, n, Expired);
446
447 const auto locker = qt_scoped_lock(d->mutex);
448 if (n > d->avail)
449 return false;
450 d->avail -= n;
451 return true;
452}
453
454/*!
455 \fn QSemaphore::tryAcquire(int n, int timeout)
456
457 Tries to acquire \c n resources guarded by the semaphore and
458 returns \c true on success. If available() < \a n, this call will
459 wait for at most \a timeout milliseconds for resources to become
460 available.
461
462 Note: Passing a negative number as the \a timeout is equivalent to
463 calling acquire(), i.e. this function will wait forever for
464 resources to become available if \a timeout is negative.
465
466 Example:
467
468 \snippet code/src_corelib_thread_qsemaphore.cpp 3
469
470 \sa acquire()
471*/
472
473/*!
474 \since 6.6
475
476 Tries to acquire \c n resources guarded by the semaphore and returns \c
477 true on success. If available() < \a n, this call will wait until \a timer
478 expires for resources to become available.
479
480 Example:
481
482 \snippet code/src_corelib_thread_qsemaphore.cpp tryAcquire-QDeadlineTimer
483
484 \sa acquire()
485*/
486bool QSemaphore::tryAcquire(int n, QDeadlineTimer timer)
487{
488 if (timer.isForever()) {
489 acquire(n);
490 return true;
491 }
492
493 if (timer.hasExpired())
494 return tryAcquire(n);
495
496 Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
497
498 if (futexAvailable())
499 return futexSemaphoreTryAcquire(u, n, timer);
500
501 using namespace std::chrono;
502 const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; };
503
504 auto locker = qt_unique_lock(d->mutex);
505 if (!d->cond.wait_until(locker, timer.deadline<steady_clock>(), sufficientResourcesAvailable))
506 return false;
507 d->avail -= n;
508 return true;
509}
510
511/*!
512 \fn template <typename Rep, typename Period> QSemaphore::tryAcquire(int n, std::chrono::duration<Rep, Period> timeout)
513 \overload
514 \since 6.3
515*/
516
517/*!
518 \fn bool QSemaphore::try_acquire()
519 \since 6.3
520
521 This function is provided for \c{std::counting_semaphore} compatibility.
522
523 It is equivalent to calling \c{tryAcquire(1)}, where the function returns
524 \c true on acquiring the resource successfully.
525
526 \sa tryAcquire(), try_acquire_for(), try_acquire_until()
527*/
528
529/*!
530 \fn template <typename Rep, typename Period> bool QSemaphore::try_acquire_for(const std::chrono::duration<Rep, Period> &timeout)
531 \since 6.3
532
533 This function is provided for \c{std::counting_semaphore} compatibility.
534
535 It is equivalent to calling \c{tryAcquire(1, timeout)}, where the call
536 times out on the given \a timeout value. The function returns \c true
537 on acquiring the resource successfully.
538
539 \sa tryAcquire(), try_acquire(), try_acquire_until()
540*/
541
542/*!
543 \fn template <typename Clock, typename Duration> bool QSemaphore::try_acquire_until(const std::chrono::time_point<Clock, Duration> &tp)
544 \since 6.3
545
546 This function is provided for \c{std::counting_semaphore} compatibility.
547
548 It is equivalent to calling \c{tryAcquire(1, tp - Clock::now())},
549 which means that the \a tp (time point) is recorded, ignoring the
550 adjustments to \c{Clock} while waiting. The function returns \c true
551 on acquiring the resource successfully.
552
553 \sa tryAcquire(), try_acquire(), try_acquire_for()
554*/
555
556/*!
557 \class QSemaphoreReleaser
558 \brief The QSemaphoreReleaser class provides exception-safe deferral of a QSemaphore::release() call.
559 \since 5.10
560 \ingroup thread
561 \inmodule QtCore
562
563 \reentrant
564
565 QSemaphoreReleaser can be used wherever you would otherwise use
566 QSemaphore::release(). Constructing a QSemaphoreReleaser defers the
567 release() call on the semaphore until the QSemaphoreReleaser is
568 destroyed (see
569 \l{http://en.cppreference.com/w/cpp/language/raii}{RAII pattern}).
570
571 You can use this to reliably release a semaphore to avoid dead-lock
572 in the face of exceptions or early returns:
573
574 \snippet code/src_corelib_thread_qsemaphore.cpp 4
575
576 If an early return is taken or an exception is thrown before the
577 \c{sem.release()} call is reached, the semaphore is not released,
578 possibly preventing the thread waiting in the corresponding
579 \c{sem.acquire()} call from ever continuing execution.
580
581 When using RAII instead:
582
583 \snippet code/src_corelib_thread_qsemaphore.cpp 5
584
585 this can no longer happen, because the compiler will make sure that
586 the QSemaphoreReleaser destructor is always called, and therefore
587 the semaphore is always released.
588
589 QSemaphoreReleaser is move-enabled and can therefore be returned
590 from functions to transfer responsibility for releasing a semaphore
591 out of a function or a scope:
592
593 \snippet code/src_corelib_thread_qsemaphore.cpp 6
594
595 A QSemaphoreReleaser can be canceled by a call to cancel(). A canceled
596 semaphore releaser will no longer call QSemaphore::release() in its
597 destructor.
598
599 \sa QMutexLocker
600*/
601
602/*!
603 \fn QSemaphoreReleaser::QSemaphoreReleaser()
604
605 Default constructor. Creates a QSemaphoreReleaser that does nothing.
606*/
607
608/*!
609 \fn QSemaphoreReleaser::QSemaphoreReleaser(QSemaphore &sem, int n)
610
611 Constructor. Stores the arguments and calls \a{sem}.release(\a{n})
612 in the destructor.
613*/
614
615/*!
616 \fn QSemaphoreReleaser::QSemaphoreReleaser(QSemaphore *sem, int n)
617
618 Constructor. Stores the arguments and calls \a{sem}->release(\a{n})
619 in the destructor.
620*/
621
622/*!
623 \fn QSemaphoreReleaser::QSemaphoreReleaser(QSemaphoreReleaser &&other)
624
625 Move constructor. Takes over responsibility to call QSemaphore::release()
626 from \a other, which in turn is canceled.
627
628 \sa cancel()
629*/
630
631/*!
632 \fn QSemaphoreReleaser::operator=(QSemaphoreReleaser &&other)
633
634 Move assignment operator. Takes over responsibility to call QSemaphore::release()
635 from \a other, which in turn is canceled.
636
637 If this semaphore releaser had the responsibility to call some QSemaphore::release()
638 itself, it performs the call before taking over from \a other.
639
640 \sa cancel()
641*/
642
643/*!
644 \fn QSemaphoreReleaser::~QSemaphoreReleaser()
645
646 Unless canceled, calls QSemaphore::release() with the arguments provided
647 to the constructor, or by the last move assignment.
648*/
649
650/*!
651 \fn QSemaphoreReleaser::swap(QSemaphoreReleaser &other)
652
653 Exchanges the responsibilities of \c{*this} and \a other.
654
655 Unlike move assignment, neither of the two objects ever releases its
656 semaphore, if any, as a consequence of swapping.
657
658 Therefore this function is very fast and never fails.
659*/
660
661/*!
662 \fn QSemaphoreReleaser::semaphore() const
663
664 Returns a pointer to the QSemaphore object provided to the constructor,
665 or by the last move assignment, if any. Otherwise, returns \nullptr.
666*/
667
668/*!
669 \fn QSemaphoreReleaser::cancel()
670
671 Cancels this QSemaphoreReleaser such that the destructor will no longer
672 call \c{semaphore()->release()}. Returns the value of semaphore()
673 before this call. After this call, semaphore() will return \nullptr.
674
675 To enable again, assign a new QSemaphoreReleaser:
676
677 \snippet code/src_corelib_thread_qsemaphore.cpp 7
678*/
679
680#else // #if QT_CONFIG(thread)
681
682// No-thread stubs for QSemaphore. These essentially allow
683// unlimited acquire and release, since we can't ever block
684// the calling thread (which is the only thread in the no-thread
685// configuraton)
686
687QSemaphore::QSemaphore(int n)
688{
689
690}
691
692QSemaphore::~QSemaphore()
693{
694
695}
696
697void QSemaphore::acquire(int)
698{
699
700}
701
702void QSemaphore::release(int)
703{
704
705}
706
707#endif
708
709QT_END_NAMESPACE