@@ -70,7 +70,15 @@ class Semaphore {
7070 public:
7171
7272 /* *
73- @brief constructs a semaphore with the given counter
73+ @brief constructs a default semaphore with count equal to zero
74+
75+ Application can use tf::Semaphore::reset to reset the counter of
76+ the semaphore later.
77+ */
78+ Semaphore () : _count(0 ) { };
79+
80+ /* *
81+ @brief constructs a semaphore with the given count
7482
7583 A semaphore creates a constraint that limits the maximum concurrency,
7684 i.e., the number of workers, in a set of tasks.
@@ -83,9 +91,11 @@ class Semaphore {
8391 }
8492
8593 /* *
86- @brief queries the counter value (not thread-safe during the run)
94+ @brief queries the current value of the associated counter
8795
8896 @param memory_order the memory order of this load (default std::memory_order_relaxed)
97+
98+ Queries the current value of the associated counter.
8999 */
90100 size_t count (std::memory_order memory_order = std::memory_order_relaxed) const {
91101 return _count.load (memory_order);
@@ -96,6 +106,8 @@ class Semaphore {
96106
97107 @return @c true if it decremented the internal counter, otherwise @c false
98108
109+ Tries to atomically decrement the internal counter by @c 1. If the operation succeeds,
110+ returns @c true, otherwise @c false.
99111 */
100112 bool try_acquire () {
101113 auto old = _count.load (std::memory_order_acquire);
@@ -112,7 +124,9 @@ class Semaphore {
112124
113125 @param n the value by which the internal counter will be incremented
114126 @return @c true if it decremented the internal counter, otherwise @c false
115-
127+
128+ The release operation always succeeds as it simply increments
129+ the counter of this semaphore.
116130 */
117131 void release (size_t n = 1 ) {
118132 _count.fetch_add (n, std::memory_order_release);
@@ -123,6 +137,11 @@ class Semaphore {
123137
124138 @param count the new count value
125139 @param memory_order memory order to which this operation will be applied
140+
141+ @note
142+ Calling tf::Semaphore::reset will immediately change the underlying
143+ counter to the given @c count value, regardless other threads acquiring
144+ or releasing the semaphore.
126145 */
127146 void reset (size_t count, std::memory_order memory_order = std::memory_order_relaxed) {
128147 _count.store (count, memory_order);
@@ -135,15 +154,28 @@ class Semaphore {
135154
136155/* *
137156@brief tries to acquire all semaphores in the specified range
157+
158+ @tparam I iterator type
159+ @param first iterator to the beginning (inclusive)
160+ @param last iterator to the end (exclusive)
161+
162+ Tries to acquire all semaphores in the specified range.
163+
164+ @return @c true if all semaphores are acquired, otherwise @c false
138165*/
139166template <typename I,
140167 std::enable_if_t <std::is_same_v<deref_t <I>, Semaphore>, void > * = nullptr
141168>
142- bool try_acquire (I begin, I end) {
143- I ptr = begin;
144- for (; ptr != end; ptr++) {
169+ bool try_acquire (I first, I last) {
170+ // Ideally, we should use a better deadlock-avoidance algorithm but
171+ // in practice the number of semaphores is small and
172+ // tf::Semaphore does not provide blocking require. Hence, we are
173+ // mostly safe here. This is similar to the GCC try_lock implementation:
174+ // https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/mutex
175+ I ptr = first;
176+ for (; ptr != last; ptr++) {
145177 if (ptr->try_acquire () == false ) {
146- for (I ptr2 = begin ; ptr2 != ptr; ptr2++) {
178+ for (I ptr2 = first ; ptr2 != ptr; ptr2++) {
147179 ptr2->release ();
148180 }
149181 return false ;
@@ -154,11 +186,22 @@ bool try_acquire(I begin, I end) {
154186
155187/* *
156188@brief tries to acquire all semaphores
189+
190+ @param semaphores semaphores to acquire
191+
192+ Tries to acquire all the semaphores.
193+
194+ @return @c true if all semaphores are acquired, otherwise @c false
157195*/
158196template <typename ... S,
159197 std::enable_if_t <all_same_v<Semaphore, std::decay_t <S>...>, void >* = nullptr
160198>
161- bool try_acquire (S&&... semaphores) {
199+ bool try_acquire (S&... semaphores) {
200+ // Ideally, we should use a better deadlock-avoidance algorithm but
201+ // in practice the number of semaphores is small and
202+ // tf::Semaphore does not provide blocking require. Hence, we are
203+ // mostly safe here. This is similar to the GCC try_lock implementation:
204+ // https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/mutex
162205 constexpr size_t N = sizeof ...(S);
163206 std::array<Semaphore*, N> items { std::addressof (semaphores)... };
164207 size_t i = 0 ;
@@ -173,5 +216,37 @@ bool try_acquire(S&&... semaphores) {
173216 return true ;
174217}
175218
219+ /* *
220+ @brief tries to acquire all semaphores in the specified range
221+
222+ @tparam I iterator type
223+ @param first iterator to the beginning (inclusive)
224+ @param last iterator to the end (exclusive)
225+
226+ Releases all the semaphores in the given range.
227+ */
228+ template <typename I,
229+ std::enable_if_t <std::is_same_v<deref_t <I>, Semaphore>, void > * = nullptr
230+ >
231+ void release (I first, I last) {
232+ std::for_each (first, last, [](tf::Semaphore& semaphore){
233+ semaphore.release ();
234+ });
235+ }
236+
237+ /* *
238+ @brief tries to acquire all semaphores
239+
240+ @param semaphores semaphores to release
241+
242+ Releases all the semaphores.
243+ */
244+ template <typename ... S,
245+ std::enable_if_t <all_same_v<Semaphore, std::decay_t <S>...>, void >* = nullptr
246+ >
247+ void release (S&... semaphores) {
248+ (semaphores.release (), ...);
249+ }
250+
176251} // end of namespace tf. ---------------------------------------------------
177252
0 commit comments