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
qlatch.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 Intel Corporation.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qlatch_p.h"
6
8#include "qfutex_p.h"
9
11
12using namespace QtFutex;
13
14#if defined(QATOMICWAIT_USE_FALLBACK)
15static constexpr bool ForcedFallbackAtomicWait = true;
16namespace atomicwait = QtFallbackAtomicWait;
17#else
18static constexpr bool ForcedFallbackAtomicWait = false;
19namespace atomicwait = q20;
20#endif
21
22/*!
23 \class QLatch
24 \internal
25
26 Implements the same API as \c std::latch (C++20), allowing a single
27 synchronization between threads.
28
29 \section2 Typical uses
30 \section3 Waiting for threaded work to finish
31
32 For this use-case, one or more threads perform some work, which needs to
33 finish before the caller thread can proceed. For this, each worker thread
34 calls countDown() once they have finished their work, while the caller
35 thread suspends execution by calling wait().
36
37 The operation is best seen in:
38 \code
39 QLatch latch(segments);
40 int y = 0;
41 for (int i = 0; i < segments; ++i) {
42 int yn = (data->height - y) / (segments - i);
43 threadPool->start([&, y, yn]() {
44 convertSegment(y, y + yn);
45 latch.countDown();
46 });
47 y += yn;
48 }
49 latch.wait();
50 \endcode
51
52 Or, for a single thread:
53 \code
54 QLatch latch(1);
55 QMetaObject::invokeMethod(object, [&]() {
56 doSomething();
57 latch.countDown();
58 }, Qt::QueuedConnection);
59 latch.wait();
60 \endcode
61
62 In fact, the above is exactly what Qt::BlockingQueued connection does.
63
64 \section3 Synchronizing execution
65
66 For this use-case, multiple threads must reach a particular state before
67 any of them may proceed. In this case, all of them call arriveAndWait(),
68 causing all but the last one of them to suspend execution until that last
69 one also arrives.
70
71 \code
72 QLatch latch(n);
73 for (int i = 0; i < n; ++i) {
74 threadPool->start([] {
75 latch.arriveAndWait();
76 doStressfulWork();
77 });
78 }
79 \endcode
80
81 \section2 Differences from \c std::latch
82
83 \list
84 \li Uses \c{int} in the API instead of \c{ptrdiff_t} (note that the max()
85 is the same as libstdc++'s on Linux).
86 \li count_down() is not \c{const} (libstdc++ implementation is).
87 \endlist
88
89 \omit
90 \section2 Implementation details
91
92 countDown() must call wakeUp() if the latch counter reaches zero and there
93 are threads waiting to be woken up. Or, conversely, countDown() needs to do
94 nothing after decrementing if the latch counter is still non-zero or there
95 are no waiters. Therefore, we choose the bits so that a non-zero
96 \c{counter} member implies no action required.
97
98 \endomit
99*/
100
101/*!
102 \fn QLatch::QLatch(int expected) noexcept
103
104 Initializes the QLatch to indicate that countDown() will be called \a
105 expected times. You probably want to pass a value greater than zero.
106*/
107
108/*!
109 \fn int QLatch::pending() noexcept
110 \internal
111
112 Returns the counter.
113
114 Don't use; for the unit test only.
115*/
116
117/*!
118 \fn void QLatch::countDown(int n) noexcept
119 \fn void QLatch::count_down(int n) noexcept
120
121 Decrements the internal counter by \a n. If the internal counter drops to
122 zero after this operation, any threads currently waiting will be woken up.
123 If \a n is greater than the value of the internal counter or is negative,
124 the behavior is undefined.
125
126 This function does not block and may be used to notify waiters that this
127 thread has reached a particular point and they may proceed. To synchronize
128 all threads so they all resume work at the same time, use arriveAndWait().
129
130 This function implements release memory ordering.
131
132 \sa arriveAndWait(), wait()
133*/
134
135/*!
136 \fn bool QLatch::tryWait() const noexcept
137 \fn void QLatch::try_wait() const noexcept
138
139 Returns true if the internal counter in this latch has dropped to zero,
140 false otherwise. This function does not block.
141
142 This function implements acquire memory ordering.
143
144 \sa wait(), countDown()
145*/
146
147/*!
148 \fn void QLatch::wait() noexcept
149
150 Waits for the internal counter in this latch to drop to zero.
151
152 This function implements acquire memory ordering.
153
154 \sa tryWait(), arriveAndWait(), countDown()
155*/
156
157/*!
158 \fn void QLatch::arriveAndWait(int n) noexcept
159 \fn void QLatch::arrive_and_wait(int n) noexcept
160
161 This function decrements the internal counter by \a n. If the counter
162 remains non-zero after this operation, it suspends the current thread until
163 it does become zero. Otherwise it wakes all other current waiters.
164
165 This function is useful to synchronize multiple threads so they may start
166 some execution at (nearly) exactly the same time.
167
168 This function is exactly equivalent to:
169 \code
170 countDown(n);
171 wait();
172 \endcode
173
174 This function implements acquire-and-release memory ordering.
175
176 \sa countDown(), wait()
177*/
178
179/*!
180 \fn int QLatch::max() noexcept
181
182 Returns the maximum number that can be passed to the constructor.
183*/
184
185void QLatch::waitInternal(int current) noexcept
186{
187 // mark that there is a waiter -> clear the bit that there are no waiters
188 if (current & NoWaiters) {
189#if __has_builtin(__atomic_and_fetch)
190 // Modern GCC and Clang are able to generate loop-free code for this
191 // operation on x86-64, ARMv8.1 and RISC-V.
192 if (__atomic_and_fetch(reinterpret_cast<int *>(&counter._q_value), ~NoWaiters,
193 int(std::memory_order_relaxed)) == 0)
194 return;
195#else
196 // Do it in two steps, which is usually better than a compare_exchange
197 // loop. This is not exactly the same as above (it's not atomic!) but
198 // is correct for our purposes because the counter never changes from 0
199 // once it reaches that.
200 counter.fetchAndAndRelaxed(~NoWaiters);
201 if (counter.loadRelaxed() == 0)
202 return; // no need to wait!
203#endif
204 }
205 current &= ~NoWaiters;
206
207 auto waitLoop = [&](auto waiter) {
208 do {
209 waiter(current);
210 } while ((current = counter.loadAcquire()) != 0);
211 };
212
213 if (futexAvailable() && !ForcedFallbackAtomicWait)
214 waitLoop([&](int current) { futexWait(counter, current); });
215 else
216 waitLoop([&](int current) {
217 atomicwait::atomic_wait_explicit(&counter._q_value, current, std::memory_order_relaxed);
218 });
219}
220
221void QLatch::wakeUp() noexcept
222{
223 if (futexAvailable() && !ForcedFallbackAtomicWait)
224 futexWakeAll(counter);
225 else
226 atomicwait::atomic_notify_all(&counter._q_value);
227}
228
229QT_END_NAMESPACE
#define __has_builtin(x)
static constexpr bool ForcedFallbackAtomicWait
Definition qlatch.cpp:18