libstdc++
semaphore_base.h
Go to the documentation of this file.
1// -*- C++ -*- header.
2
3// Copyright (C) 2020-2025 Free Software Foundation, Inc.
4//
5// This file is part of the GNU ISO C++ Library. This library is free
6// software; you can redistribute it and/or modify it under the
7// terms of the GNU General Public License as published by the
8// Free Software Foundation; either version 3, or (at your option)
9// any later version.
10
11// This library is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// Under Section 7 of GPL version 3, you are granted additional
17// permissions described in the GCC Runtime Library Exception, version
18// 3.1, as published by the Free Software Foundation.
19
20// You should have received a copy of the GNU General Public License and
21// a copy of the GCC Runtime Library Exception along with this program;
22// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
23// <http://www.gnu.org/licenses/>.
24
25/** @file bits/semaphore_base.h
26 * This is an internal header file, included by other library headers.
27 * Do not attempt to use it directly. @headername{semaphore}
28 */
29
30#ifndef _GLIBCXX_SEMAPHORE_BASE_H
31#define _GLIBCXX_SEMAPHORE_BASE_H 1
32
33#ifdef _GLIBCXX_SYSHDR
34#pragma GCC system_header
35#endif
36
37#include <bits/version.h>
38
39#ifdef __glibcxx_semaphore // C++ >= 20 && hosted && atomic_wait
40#include <bits/atomic_base.h>
41#include <bits/chrono.h>
43#include <ext/numeric_traits.h>
44
45namespace std _GLIBCXX_VISIBILITY(default)
46{
47_GLIBCXX_BEGIN_NAMESPACE_VERSION
48
49 struct __semaphore_impl
50 {
51 using __count_type = ptrdiff_t;
52
53 static constexpr ptrdiff_t _S_max
54 = __gnu_cxx::__int_traits<__count_type>::__max;
55
56 constexpr explicit
57 __semaphore_impl(__count_type __count) noexcept
58 : _M_counter(__count)
59 { }
60
61 __semaphore_impl(const __semaphore_impl&) = delete;
62 __semaphore_impl& operator=(const __semaphore_impl&) = delete;
63
64 // Load the current counter value.
65 _GLIBCXX_ALWAYS_INLINE __count_type
66 _M_get_current() const noexcept
67 { return __atomic_impl::load(&_M_counter, memory_order::acquire); }
68
69 // Try to acquire the semaphore (i.e. decrement the counter).
70 // Returns false if the current counter is zero, or if another thread
71 // changes the value first. In the latter case, __cur is set to the new
72 // value.
73 _GLIBCXX_ALWAYS_INLINE bool
74 _M_do_try_acquire(__count_type& __cur) noexcept
75 {
76 if (__cur == 0)
77 return false; // Cannot decrement when it's already zero.
78
79 return __atomic_impl::compare_exchange_strong(&_M_counter,
80 __cur, __cur - 1,
81 memory_order::acquire,
82 memory_order::relaxed);
83 }
84
85 // Keep trying to acquire the semaphore in a loop until it succeeds.
86 void
87 _M_acquire() noexcept
88 {
89 auto __vfn = [this]{ return _M_get_current(); };
90 _Available __is_available{__vfn()};
91 while (!_M_do_try_acquire(__is_available._M_val))
92 if (!__is_available())
93 std::__atomic_wait_address(&_M_counter, __is_available, __vfn, true);
94 }
95
96 // Try to acquire the semaphore, retrying a small number of times
97 // in case of contention.
98 bool
99 _M_try_acquire() noexcept
100 {
101 // The fastest implementation of this function is just _M_do_try_acquire
102 // but that can fail under contention even when _M_count > 0.
103 // Using _M_try_acquire_for(0ns) will retry a few times in a loop.
104 return _M_try_acquire_for(__detail::__wait_clock_t::duration{});
105 }
106
107 template<typename _Clock, typename _Duration>
108 bool
109 _M_try_acquire_until(const chrono::time_point<_Clock, _Duration>& __atime) noexcept
110 {
111 auto __vfn = [this]{ return _M_get_current(); };
112 _Available __is_available{__vfn()};
113 while (!_M_do_try_acquire(__is_available._M_val))
114 if (!__is_available())
115 if (!std::__atomic_wait_address_until(&_M_counter, __is_available,
116 __vfn, __atime, true))
117 return false; // timed out
118 return true;
119 }
120
121 template<typename _Rep, typename _Period>
122 bool
123 _M_try_acquire_for(const chrono::duration<_Rep, _Period>& __rtime) noexcept
124 {
125 auto __vfn = [this]{ return _M_get_current(); };
126 _Available __is_available{__vfn()};
127 while (!_M_do_try_acquire(__is_available._M_val))
128 if (!__is_available())
129 if (!std::__atomic_wait_address_for(&_M_counter, __is_available,
130 __vfn, __rtime, true))
131 return false; // timed out
132 return true;
133 }
134
135 _GLIBCXX_ALWAYS_INLINE ptrdiff_t
136 _M_release(ptrdiff_t __update) noexcept
137 {
138 auto __old = __atomic_impl::fetch_add(&_M_counter, __update,
139 memory_order::release);
140 if (__old == 0 && __update > 0)
141 __atomic_notify_address(&_M_counter, true, true);
142 return __old;
143 }
144
145 private:
146 struct _Available
147 {
148 __count_type _M_val; // Cache of the last value loaded from _M_counter.
149
150 // Returns true if the cached value is non-zero and so it should be
151 // possible to acquire the semaphore.
152 bool operator()() const noexcept { return _M_val > 0; }
153
154 // Argument should be the latest value of the counter.
155 // Returns true (and caches the value) if it's non-zero, meaning it
156 // should be possible to acquire the semaphore. Returns false otherwise.
157 bool operator()(__count_type __cur) noexcept
158 {
159 if (__cur == 0)
160 return false;
161 _M_val = __cur;
162 return true;
163 }
164 };
165
166 alignas(__atomic_ref<__count_type>::required_alignment)
167 __count_type _M_counter;
168 };
169
170 // Optimized specialization using __platform_wait (if available)
171 template<bool _Binary>
172 struct __platform_semaphore_impl
173 {
174 using __count_type = __detail::__platform_wait_t;
175
176 static consteval ptrdiff_t
177 _S_calc_max()
178 {
179 if (_Binary)
180 return 1;
181 else if ((ptrdiff_t)__gnu_cxx::__int_traits<__count_type>::__max < 0)
182 return __gnu_cxx::__int_traits<ptrdiff_t>::__max;
183 else
184 return __gnu_cxx::__int_traits<__count_type>::__max;
185 }
186
187 static constexpr ptrdiff_t _S_max = _S_calc_max();
188
189 constexpr explicit
190 __platform_semaphore_impl(__count_type __count) noexcept
191 : _M_counter(__count)
192 { }
193
194 __platform_semaphore_impl(__platform_semaphore_impl&) = delete;
195 __platform_semaphore_impl& operator=(const __platform_semaphore_impl&) = delete;
196
197 // Load the current counter value.
198 _GLIBCXX_ALWAYS_INLINE __count_type
199 _M_get_current() const noexcept
200 {
201 if constexpr (_Binary)
202 return 1; // Not necessarily true, but optimistically assume it is.
203 else
204 return __atomic_impl::load(&_M_counter, memory_order::acquire);
205 }
206
207 // Try to acquire the semaphore (i.e. decrement the counter).
208 // Returns false if the current counter is zero, or if another thread
209 // changes the value first. In the latter case, __cur is set to the new
210 // value.
211 _GLIBCXX_ALWAYS_INLINE bool
212 _M_do_try_acquire(__count_type& __cur) noexcept
213 {
214 if (__cur == 0)
215 return false; // Cannot decrement when it's already zero.
216
217 return __atomic_impl::compare_exchange_strong(&_M_counter,
218 __cur, __cur - 1,
219 memory_order::acquire,
220 memory_order::relaxed);
221 }
222
223 // Keep trying to acquire the semaphore in a loop until it succeeds.
224 void
225 _M_acquire() noexcept
226 {
227 auto __val = _M_get_current();
228 while (!_M_do_try_acquire(__val))
229 if (__val == 0)
230 {
231 std::__atomic_wait_address_v(&_M_counter, __val, __ATOMIC_ACQUIRE,
232 true);
233 __val = _M_get_current();
234 }
235 }
236
237 // Try to acquire the semaphore.
238 bool
239 _M_try_acquire() noexcept
240 {
241 if constexpr (_Binary)
242 {
243 __count_type __val = 1;
244 // Do not expect much contention on binary semaphore, only try once.
245 return _M_do_try_acquire(__val);
246 }
247 else
248 // Fastest implementation of this function is just _M_do_try_acquire
249 // but that can fail under contention even when _M_count > 0.
250 // Using _M_try_acquire_for(0ns) will retry a few times in a loop.
251 return _M_try_acquire_for(__detail::__wait_clock_t::duration{});
252 }
253
254 template<typename _Clock, typename _Duration>
255 bool
256 _M_try_acquire_until(const chrono::time_point<_Clock, _Duration>& __atime) noexcept
257 {
258 auto __val = _M_get_current();
259 while (!_M_do_try_acquire(__val))
260 if (__val == 0)
261 {
262 if (!std::__atomic_wait_address_until_v(&_M_counter, 0,
263 __ATOMIC_ACQUIRE,
264 __atime, true))
265 return false; // timed out
266 __val = _M_get_current();
267 }
268 return true;
269 }
270
271 template<typename _Rep, typename _Period>
272 bool
273 _M_try_acquire_for(const chrono::duration<_Rep, _Period>& __rtime) noexcept
274 {
275 auto __val = _M_get_current();
276 while (!_M_do_try_acquire(__val))
277 if (__val == 0)
278 {
279 if (!std::__atomic_wait_address_for_v(&_M_counter, 0,
280 __ATOMIC_ACQUIRE,
281 __rtime, true))
282 return false; // timed out
283 __val = _M_get_current();
284 }
285 return true;
286 }
287
288 _GLIBCXX_ALWAYS_INLINE ptrdiff_t
289 _M_release(ptrdiff_t __update) noexcept
290 {
291 auto __old = __atomic_impl::fetch_add(&_M_counter, __update,
292 memory_order::release);
293 if (__old == 0 && __update > 0)
294 __atomic_notify_address(&_M_counter, true, true);
295 return __old;
296 }
297
298 protected:
299 alignas(__detail::__platform_wait_alignment) __count_type _M_counter;
300 };
301
302 template<ptrdiff_t _Max, typename _Tp = __detail::__platform_wait_t>
303 using _Semaphore_impl
304 = __conditional_t<__platform_wait_uses_type<_Tp>
305 && _Max <= __gnu_cxx::__int_traits<_Tp>::__max,
306 __platform_semaphore_impl<(_Max <= 1)>,
307 __semaphore_impl>;
308
309_GLIBCXX_END_NAMESPACE_VERSION
310} // namespace std
311#endif // __glibcxx_semaphore
312#endif // _GLIBCXX_SEMAPHORE_BASE_H
ISO C++ entities toplevel namespace is std.