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