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 constexpr ptrdiff_t _S_max
177 = _Binary ? 1 : __gnu_cxx::__int_traits<__count_type>::__max;
178
179 constexpr explicit
180 __platform_semaphore_impl(__count_type __count) noexcept
181 : _M_counter(__count)
182 { }
183
184 __platform_semaphore_impl(__platform_semaphore_impl&) = delete;
185 __platform_semaphore_impl& operator=(const __platform_semaphore_impl&) = delete;
186
187 // Load the current counter value.
188 _GLIBCXX_ALWAYS_INLINE __count_type
189 _M_get_current() const noexcept
190 {
191 if constexpr (_Binary)
192 return 1; // Not necessarily true, but optimistically assume it is.
193 else
194 return __atomic_impl::load(&_M_counter, memory_order::acquire);
195 }
196
197 // Try to acquire the semaphore (i.e. decrement the counter).
198 // Returns false if the current counter is zero, or if another thread
199 // changes the value first. In the latter case, __cur is set to the new
200 // value.
201 _GLIBCXX_ALWAYS_INLINE bool
202 _M_do_try_acquire(__count_type& __cur) noexcept
203 {
204 if (__cur == 0)
205 return false; // Cannot decrement when it's already zero.
206
207 return __atomic_impl::compare_exchange_strong(&_M_counter,
208 __cur, __cur - 1,
209 memory_order::acquire,
210 memory_order::relaxed);
211 }
212
213 // Keep trying to acquire the semaphore in a loop until it succeeds.
214 void
215 _M_acquire() noexcept
216 {
217 auto __val = _M_get_current();
218 while (!_M_do_try_acquire(__val))
219 if (__val == 0)
220 {
221 std::__atomic_wait_address_v(&_M_counter, __val, __ATOMIC_ACQUIRE,
222 true);
223 __val = _M_get_current();
224 }
225 }
226
227 // Try to acquire the semaphore.
228 bool
229 _M_try_acquire() noexcept
230 {
231 if constexpr (_Binary)
232 {
233 __count_type __val = 1;
234 // Do not expect much contention on binary semaphore, only try once.
235 return _M_do_try_acquire(__val);
236 }
237 else
238 // Fastest implementation of this function is just _M_do_try_acquire
239 // but that can fail under contention even when _M_count > 0.
240 // Using _M_try_acquire_for(0ns) will retry a few times in a loop.
241 return _M_try_acquire_for(__detail::__wait_clock_t::duration{});
242 }
243
244 template<typename _Clock, typename _Duration>
245 bool
246 _M_try_acquire_until(const chrono::time_point<_Clock, _Duration>& __atime) noexcept
247 {
248 auto __val = _M_get_current();
249 while (!_M_do_try_acquire(__val))
250 if (__val == 0)
251 {
252 if (!std::__atomic_wait_address_until_v(&_M_counter, 0,
253 __ATOMIC_ACQUIRE,
254 __atime, true))
255 return false; // timed out
256 __val = _M_get_current();
257 }
258 return true;
259 }
260
261 template<typename _Rep, typename _Period>
262 bool
263 _M_try_acquire_for(const chrono::duration<_Rep, _Period>& __rtime) noexcept
264 {
265 auto __val = _M_get_current();
266 while (!_M_do_try_acquire(__val))
267 if (__val == 0)
268 {
269 if (!std::__atomic_wait_address_for_v(&_M_counter, 0,
270 __ATOMIC_ACQUIRE,
271 __rtime, true))
272 return false; // timed out
273 __val = _M_get_current();
274 }
275 return true;
276 }
277
278 _GLIBCXX_ALWAYS_INLINE ptrdiff_t
279 _M_release(ptrdiff_t __update) noexcept
280 {
281 auto __old = __atomic_impl::fetch_add(&_M_counter, __update,
282 memory_order::release);
283 if (__old == 0 && __update > 0)
284 __atomic_notify_address(&_M_counter, true, true);
285 return __old;
286 }
287
288 protected:
289 alignas(__detail::__platform_wait_alignment) __count_type _M_counter;
290 };
291
292 template<ptrdiff_t _Max, typename _Tp = __detail::__platform_wait_t>
293 using _Semaphore_impl
294 = __conditional_t<__platform_wait_uses_type<_Tp>
295 && _Max <= __gnu_cxx::__int_traits<_Tp>::__max,
296 __platform_semaphore_impl<(_Max <= 1)>,
297 __semaphore_impl>;
298
299_GLIBCXX_END_NAMESPACE_VERSION
300} // namespace std
301#endif // __glibcxx_semaphore
302#endif // _GLIBCXX_SEMAPHORE_BASE_H
ISO C++ entities toplevel namespace is std.