VLink  2.0.0
A high-performance communication middleware
condition_variable.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file condition_variable.h
26  * @brief Monotonic-clock condition variable replacement immune to system clock jumps.
27  *
28  * @details
29  * Older libstdc++ implementations route every @c std::condition_variable timed wait through
30  * @c CLOCK_REALTIME, so an NTP step or manual date change can spuriously wake or starve waiters
31  * (GCC PR 41861 / DR 887). On POSIX systems this header substitutes a hand-rolled
32  * @c vlink::ConditionVariable backed by a @c pthread_cond_t configured with
33  * @c pthread_condattr_setclock @c (..., @c CLOCK_MONOTONIC). On Windows the bug does not apply
34  * and the names alias to the standard library types verbatim.
35  *
36  * @par API surface vs @c std::condition_variable
37  *
38  * | Aspect | @c std::condition_variable | @c vlink::ConditionVariable |
39  * | ----------------- | --------------------------------------- | ------------------------------------------ |
40  * | Backing clock | @c CLOCK_REALTIME (libstdc++) | @c CLOCK_MONOTONIC via @c pthread_condattr |
41  * | Timed waits | Sensitive to wall clock jumps | Immune to wall clock jumps |
42  * | Copy / move | Deleted | Deleted |
43  * | Public methods | wait / wait_for / wait_until / notify_* | Same signatures, same return types |
44  * | Native handle | @c pthread_cond_t* | @c pthread_cond_t* |
45  *
46  * @par Wait / notify sequence
47  *
48  * @verbatim
49  * producer thread consumer thread
50  * --------------- ---------------
51  * lock(mtx) lock(mtx)
52  * state = ready cv.wait(lock, predicate)
53  * unlock(mtx) releases mtx, blocks
54  * cv.notify_one() -----------> wakes; reacquires mtx
55  * re-checks predicate
56  * returns to caller
57  * @endverbatim
58  *
59  * Both @c ConditionVariable and @c ConditionVariableAny accept any @c std::chrono clock type;
60  * non-steady clock arguments are projected onto @c steady_clock at entry and re-checked at exit
61  * for timeout fidelity. Convenience type aliases @c vlink::condition_variable and
62  * @c vlink::condition_variable_any are also exposed.
63  *
64  * @par Example
65  * @code
66  * std::mutex mtx;
67  * vlink::ConditionVariable cv;
68  * bool ready = false;
69  *
70  * // Consumer:
71  * {
72  * std::unique_lock lock(mtx);
73  * cv.wait_for(lock, std::chrono::milliseconds(200), [&] { return ready; });
74  * }
75  *
76  * // Producer:
77  * {
78  * std::lock_guard lock(mtx);
79  * ready = true;
80  * }
81  * cv.notify_one();
82  * @endcode
83  */
84 
85 #pragma once
86 
87 #include <condition_variable>
88 
89 #if !defined(VLINK_ENABLE_BASE_CONDITION) && defined(__unix__) && !defined(__CYGWIN__)
90 #define VLINK_ENABLE_BASE_CONDITION
91 #endif
92 
93 #ifdef VLINK_ENABLE_BASE_CONDITION
94 #include <pthread.h>
95 
96 #include <chrono>
97 #include <memory>
98 #include <mutex>
99 #include <utility>
100 
101 #include "./macros.h"
102 
103 namespace vlink {
104 
105 /**
106  * @class ConditionVariable
107  * @brief pthread-backed condition variable using @c CLOCK_MONOTONIC for all timed waits.
108  *
109  * @details
110  * Provides the same public interface as @c std::condition_variable. Internally owns a single
111  * @c pthread_cond_t configured at construction with @c pthread_condattr_setclock so the kernel
112  * uses the monotonic clock when timing out waiters; wall-clock arguments are projected onto the
113  * monotonic clock to preserve fidelity.
114  */
115 class VLINK_EXPORT ConditionVariable final {
116  public:
117  /**
118  * @brief Native handle type returned by @c native_handle.
119  */
120  using native_handle_type = pthread_cond_t*;
121 
122  ConditionVariable(const ConditionVariable&) noexcept = delete;
123 
124  ConditionVariable& operator=(const ConditionVariable&) noexcept = delete;
125 
126  /**
127  * @brief Constructs and initialises the underlying @c pthread_cond_t with @c CLOCK_MONOTONIC.
128  */
129  ConditionVariable() noexcept;
130 
131  /**
132  * @brief Destroys the underlying @c pthread_cond_t.
133  */
134  ~ConditionVariable() noexcept;
135 
136  /**
137  * @brief Wakes a single thread currently blocked on this condition variable.
138  */
139  void notify_one() noexcept;
140 
141  /**
142  * @brief Wakes every thread currently blocked on this condition variable.
143  */
144  void notify_all() noexcept;
145 
146  /**
147  * @brief Atomically releases @p lock and suspends until a notification arrives.
148  *
149  * @param lock Held lock to release across the wait.
150  */
151  void wait(std::unique_lock<std::mutex>& lock) noexcept;
152 
153  /**
154  * @brief Waits in a predicate loop until @p p reports @c true.
155  *
156  * @tparam PredicateT Nullary callable returning @c bool.
157  * @param lock Held lock to release across the wait.
158  * @param p Predicate evaluated on each wakeup.
159  */
160  template <typename PredicateT>
161  void wait(std::unique_lock<std::mutex>& lock, PredicateT p) noexcept;
162 
163  /**
164  * @brief Steady-clock wait_until overload; the deadline is honoured directly.
165  *
166  * @tparam DurationT Duration type of the time point.
167  * @param lock Held lock to release across the wait.
168  * @param atime Absolute steady-clock deadline.
169  * @return @c std::cv_status::timeout when the deadline passed, otherwise @c no_timeout.
170  */
171  template <typename DurationT>
172  std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
173  const std::chrono::time_point<std::chrono::steady_clock, DurationT>& atime) noexcept;
174 
175  /**
176  * @brief System-clock wait_until overload; the deadline is projected onto steady-clock.
177  *
178  * @tparam DurationT Duration type of the time point.
179  * @param lock Held lock to release across the wait.
180  * @param atime Absolute system-clock deadline.
181  * @return @c std::cv_status::timeout when the deadline passed, otherwise @c no_timeout.
182  */
183  template <typename DurationT>
184  std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
185  const std::chrono::time_point<std::chrono::system_clock, DurationT>& atime) noexcept;
186 
187  /**
188  * @brief Generic clock wait_until overload; the deadline is projected onto steady-clock.
189  *
190  * @tparam ClockT Clock type of the time point.
191  * @tparam DurationT Duration type of the time point.
192  * @param lock Held lock to release across the wait.
193  * @param atime Absolute deadline in @c ClockT.
194  * @return @c std::cv_status::timeout or @c no_timeout.
195  */
196  template <typename ClockT, typename DurationT>
197  std::cv_status wait_until(std::unique_lock<std::mutex>& lock,
198  const std::chrono::time_point<ClockT, DurationT>& atime) noexcept;
199 
200  /**
201  * @brief Predicate-driven wait_until that exits when @p p reports @c true or the deadline elapses.
202  *
203  * @tparam ClockT Clock type of the deadline.
204  * @tparam DurationT Duration type of the deadline.
205  * @tparam PredicateT Nullary callable returning @c bool.
206  * @param lock Held lock to release across the wait.
207  * @param atime Absolute deadline.
208  * @param p Predicate evaluated on each wakeup.
209  * @return Final value of @p p when the call returns.
210  */
211  template <typename ClockT, typename DurationT, typename PredicateT>
212  bool wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<ClockT, DurationT>& atime,
213  PredicateT p) noexcept;
214 
215  /**
216  * @brief Relative wait_for overload anchored on the steady-clock.
217  *
218  * @tparam RepT Duration representation.
219  * @tparam PeriodT Duration period.
220  * @param lock Held lock to release across the wait.
221  * @param rtime Maximum wait duration.
222  * @return @c std::cv_status::timeout or @c no_timeout.
223  */
224  template <typename RepT, typename PeriodT>
225  std::cv_status wait_for(std::unique_lock<std::mutex>& lock,
226  const std::chrono::duration<RepT, PeriodT>& rtime) noexcept;
227 
228  /**
229  * @brief Predicate-driven wait_for that exits when @p p reports @c true or @p rtime elapses.
230  *
231  * @tparam RepT Duration representation.
232  * @tparam PeriodT Duration period.
233  * @tparam PredicateT Nullary callable returning @c bool.
234  * @param lock Held lock to release across the wait.
235  * @param rtime Maximum wait duration.
236  * @param p Predicate evaluated on each wakeup.
237  * @return Final value of @p p when the call returns.
238  */
239  template <typename RepT, typename PeriodT, typename PredicateT>
240  bool wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<RepT, PeriodT>& rtime,
241  PredicateT p) noexcept;
242 
243  /**
244  * @brief Returns the underlying @c pthread_cond_t pointer.
245  *
246  * @return Pointer to the internal native condition variable.
247  */
248  [[nodiscard]] native_handle_type native_handle() noexcept;
249 
250  private:
251  template <typename ToDurT, typename RepT, typename PeriodT>
252  static constexpr ToDurT ceil(const std::chrono::duration<RepT, PeriodT>& d) noexcept;
253 
254  template <typename TpT, typename UpT>
255  static constexpr TpT ceil_impl(const TpT& t, const UpT& u) noexcept;
256 
257  std::cv_status wait_until_steady(std::unique_lock<std::mutex>& lock,
258  const std::chrono::steady_clock::time_point& atime) noexcept;
259 
260  pthread_cond_t cond_{};
261 };
262 
263 /**
264  * @class ConditionVariableAny
265  * @brief Monotonic-clock condition variable accepting any @c BasicLockable.
266  *
267  * @details
268  * Mirrors @c std::condition_variable_any while routing all timed waits through the same
269  * pthread-backed monotonic condition variable used by @c ConditionVariable. An internal
270  * @c std::mutex pairs with the shared cv so arbitrary lockables can be unlocked across the
271  * wait and relocked on return.
272  *
273  * @note Behaviour is unspecified if destruction races with any other member function (matches
274  * @c std::condition_variable_any per @c [thread.condition.condvarany]).
275  */
277  public:
278  ConditionVariableAny(const ConditionVariableAny&) noexcept = delete;
279 
280  ConditionVariableAny& operator=(const ConditionVariableAny&) noexcept = delete;
281 
282  /**
283  * @brief Constructs the shared state and underlying condition variable.
284  */
285  ConditionVariableAny() noexcept;
286 
287  /**
288  * @brief Destructor.
289  */
290  ~ConditionVariableAny() noexcept;
291 
292  /**
293  * @brief Wakes a single thread blocked on this condition variable.
294  */
295  void notify_one() noexcept;
296 
297  /**
298  * @brief Wakes every thread blocked on this condition variable.
299  */
300  void notify_all() noexcept;
301 
302  /**
303  * @brief Atomically releases @p lock and suspends until a notification arrives.
304  *
305  * @tparam LockT Any @c BasicLockable type.
306  * @param lock Held lock to release across the wait.
307  */
308  template <typename LockT>
309  void wait(LockT& lock) noexcept;
310 
311  /**
312  * @brief Predicate wait variant that loops until @p p returns @c true.
313  *
314  * @tparam LockT Any @c BasicLockable type.
315  * @tparam PredicateT Nullary callable returning @c bool.
316  * @param lock Held lock to release across the wait.
317  * @param p Predicate evaluated on each wakeup.
318  */
319  template <typename LockT, typename PredicateT>
320  void wait(LockT& lock, PredicateT p) noexcept;
321 
322  /**
323  * @brief Generic clock wait_until variant.
324  *
325  * @tparam LockT Any @c BasicLockable type.
326  * @tparam ClockT Clock type of the deadline.
327  * @tparam DurationT Duration type of the deadline.
328  * @param lock Held lock to release across the wait.
329  * @param atime Absolute deadline.
330  * @return @c std::cv_status::timeout or @c no_timeout.
331  */
332  template <typename LockT, typename ClockT, typename DurationT>
333  std::cv_status wait_until(LockT& lock, const std::chrono::time_point<ClockT, DurationT>& atime) noexcept;
334 
335  /**
336  * @brief Predicate-driven wait_until variant.
337  *
338  * @tparam LockT Any @c BasicLockable type.
339  * @tparam ClockT Clock type of the deadline.
340  * @tparam DurationT Duration type of the deadline.
341  * @tparam PredicateT Nullary callable returning @c bool.
342  * @param lock Held lock to release across the wait.
343  * @param atime Absolute deadline.
344  * @param p Predicate evaluated on each wakeup.
345  * @return Final value of @p p when the call returns.
346  */
347  template <typename LockT, typename ClockT, typename DurationT, typename PredicateT>
348  bool wait_until(LockT& lock, const std::chrono::time_point<ClockT, DurationT>& atime, PredicateT p) noexcept;
349 
350  /**
351  * @brief Relative wait_for variant.
352  *
353  * @tparam LockT Any @c BasicLockable type.
354  * @tparam RepT Duration representation.
355  * @tparam PeriodT Duration period.
356  * @param lock Held lock to release across the wait.
357  * @param rtime Maximum wait duration.
358  * @return @c std::cv_status::timeout or @c no_timeout.
359  */
360  template <typename LockT, typename RepT, typename PeriodT>
361  std::cv_status wait_for(LockT& lock, const std::chrono::duration<RepT, PeriodT>& rtime) noexcept;
362 
363  /**
364  * @brief Predicate-driven wait_for variant.
365  *
366  * @tparam LockT Any @c BasicLockable type.
367  * @tparam RepT Duration representation.
368  * @tparam PeriodT Duration period.
369  * @tparam PredicateT Nullary callable returning @c bool.
370  * @param lock Held lock to release across the wait.
371  * @param rtime Maximum wait duration.
372  * @param p Predicate evaluated on each wakeup.
373  * @return Final value of @p p when the call returns.
374  */
375  template <typename LockT, typename RepT, typename PeriodT, typename PredicateT>
376  bool wait_for(LockT& lock, const std::chrono::duration<RepT, PeriodT>& rtime, PredicateT p) noexcept;
377 
378  private:
379  template <typename ToDurT, typename RepT, typename PeriodT>
380  static constexpr ToDurT ceil(const std::chrono::duration<RepT, PeriodT>& d) noexcept;
381 
382  template <typename TpT, typename UpT>
383  static constexpr TpT ceil_impl(const TpT& t, const UpT& u) noexcept;
384 
385  template <typename LockT, typename DurationT>
386  std::cv_status wait_until_impl(LockT& lock,
387  const std::chrono::time_point<std::chrono::steady_clock, DurationT>& atime) noexcept;
388 
389  struct SharedState final {
390  std::mutex mtx;
392  };
393 
394  std::shared_ptr<SharedState> shared_state_;
395 };
396 
397 ////////////////////////////////////////////////////////////////
398 /// Details
399 ////////////////////////////////////////////////////////////////
400 
401 template <typename PredicateT>
402 inline void ConditionVariable::wait(std::unique_lock<std::mutex>& lock, PredicateT p) noexcept {
403  while (!p()) {
404  wait(lock);
405  }
406 }
407 
408 template <typename DurationT>
409 inline std::cv_status ConditionVariable::wait_until(
410  std::unique_lock<std::mutex>& lock,
411  const std::chrono::time_point<std::chrono::steady_clock, DurationT>& atime) noexcept {
412  return wait_until_steady(lock, std::chrono::time_point_cast<std::chrono::steady_clock::duration>(atime));
413 }
414 
415 template <typename DurationT>
416 inline std::cv_status ConditionVariable::wait_until(
417  std::unique_lock<std::mutex>& lock,
418  const std::chrono::time_point<std::chrono::system_clock, DurationT>& atime) noexcept {
419  return wait_until<std::chrono::system_clock, DurationT>(lock, atime);
420 }
421 
422 template <typename ClockT, typename DurationT>
423 inline std::cv_status ConditionVariable::wait_until(std::unique_lock<std::mutex>& lock,
424  const std::chrono::time_point<ClockT, DurationT>& atime) noexcept {
425  const typename ClockT::time_point c_entry = ClockT::now();
426  const std::chrono::steady_clock::time_point s_entry = std::chrono::steady_clock::now();
427  const auto delta = atime - c_entry;
428  const auto s_atime = s_entry + ceil<std::chrono::steady_clock::duration>(delta);
429 
430  if (wait_until_steady(lock, s_atime) == std::cv_status::no_timeout) {
431  return std::cv_status::no_timeout; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
432  }
433 
434  if (ClockT::now() < atime) {
435  return std::cv_status::no_timeout; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
436  }
437 
438  return std::cv_status::timeout;
439 }
440 
441 template <typename ClockT, typename DurationT, typename PredicateT>
442 inline bool ConditionVariable::wait_until(std::unique_lock<std::mutex>& lock,
443  const std::chrono::time_point<ClockT, DurationT>& atime,
444  PredicateT p) noexcept {
445  while (!p()) {
446  if (wait_until(lock, atime) == std::cv_status::timeout) {
447  return p();
448  }
449  }
450 
451  return true;
452 }
453 
454 template <typename RepT, typename PeriodT>
455 inline std::cv_status ConditionVariable::wait_for(std::unique_lock<std::mutex>& lock,
456  const std::chrono::duration<RepT, PeriodT>& rtime) noexcept {
457  return wait_until(lock, std::chrono::steady_clock::now() + ceil<std::chrono::steady_clock::duration>(rtime));
458 }
459 
460 template <typename RepT, typename PeriodT, typename PredicateT>
461 inline bool ConditionVariable::wait_for(std::unique_lock<std::mutex>& lock,
462  const std::chrono::duration<RepT, PeriodT>& rtime, PredicateT p) noexcept {
463  return wait_until(lock, std::chrono::steady_clock::now() + ceil<std::chrono::steady_clock::duration>(rtime),
464  std::move(p));
465 }
466 
467 template <typename ToDurT, typename RepT, typename PeriodT>
468 inline constexpr ToDurT ConditionVariable::ceil(const std::chrono::duration<RepT, PeriodT>& d) noexcept {
469  return ceil_impl(std::chrono::duration_cast<ToDurT>(d), d);
470 }
471 
472 template <typename TpT, typename UpT>
473 inline constexpr TpT ConditionVariable::ceil_impl(const TpT& t, const UpT& u) noexcept {
474  return (t < u) ? (t + TpT{1}) : t;
475 }
476 
477 template <typename LockT>
478 inline void ConditionVariableAny::wait(LockT& lock) noexcept {
479  std::shared_ptr<SharedState> state = shared_state_;
480  std::unique_lock internal_lock(state->mtx);
481  lock.unlock();
482 
483  struct UnlockGuard final {
484  LockT& lock_ref;
485 
486  ~UnlockGuard() noexcept {
487  try {
488  lock_ref.lock();
489  } catch (std::exception&) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
490  }
491  }
492  } guard{lock};
493 
494  state->cv.wait(internal_lock);
495 }
496 
497 template <typename LockT, typename PredicateT>
498 inline void ConditionVariableAny::wait(LockT& lock, PredicateT p) noexcept {
499  while (!p()) {
500  wait(lock);
501  }
502 }
503 
504 template <typename LockT, typename ClockT, typename DurationT>
505 inline std::cv_status ConditionVariableAny::wait_until(
506  LockT& lock, const std::chrono::time_point<ClockT, DurationT>& atime) noexcept {
507  if constexpr (std::is_same_v<ClockT, std::chrono::steady_clock>) {
508  return wait_until_impl(lock, atime);
509  } else {
510  const typename ClockT::time_point c_entry = ClockT::now();
511  const std::chrono::steady_clock::time_point s_entry = std::chrono::steady_clock::now();
512  const auto delta = atime - c_entry;
513  const auto s_atime = s_entry + ceil<std::chrono::steady_clock::duration>(delta);
514 
515  if (wait_until_impl(lock, s_atime) == std::cv_status::no_timeout) {
516  return std::cv_status::no_timeout; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
517  }
518 
519  if (ClockT::now() < atime) {
520  return std::cv_status::no_timeout; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
521  }
522 
523  return std::cv_status::timeout;
524  }
525 }
526 
527 template <typename LockT, typename ClockT, typename DurationT, typename PredicateT>
528 inline bool ConditionVariableAny::wait_until(LockT& lock, const std::chrono::time_point<ClockT, DurationT>& atime,
529  PredicateT p) noexcept {
530  while (!p()) {
531  if (wait_until(lock, atime) == std::cv_status::timeout) {
532  return p();
533  }
534  }
535 
536  return true;
537 }
538 
539 template <typename LockT, typename RepT, typename PeriodT>
540 inline std::cv_status ConditionVariableAny::wait_for(LockT& lock,
541  const std::chrono::duration<RepT, PeriodT>& rtime) noexcept {
542  return wait_until(lock, std::chrono::steady_clock::now() + ceil<std::chrono::steady_clock::duration>(rtime));
543 }
544 
545 template <typename LockT, typename RepT, typename PeriodT, typename PredicateT>
546 inline bool ConditionVariableAny::wait_for(LockT& lock, const std::chrono::duration<RepT, PeriodT>& rtime,
547  PredicateT p) noexcept {
548  return wait_until(lock, std::chrono::steady_clock::now() + ceil<std::chrono::steady_clock::duration>(rtime),
549  std::move(p));
550 }
551 
552 template <typename ToDurT, typename RepT, typename PeriodT>
553 inline constexpr ToDurT ConditionVariableAny::ceil(const std::chrono::duration<RepT, PeriodT>& d) noexcept {
554  return ceil_impl(std::chrono::duration_cast<ToDurT>(d), d);
555 }
556 
557 template <typename TpT, typename UpT>
558 inline constexpr TpT ConditionVariableAny::ceil_impl(const TpT& t, const UpT& u) noexcept {
559  return (t < u) ? (t + TpT{1}) : t;
560 }
561 
562 template <typename LockT, typename DurationT>
563 inline std::cv_status ConditionVariableAny::wait_until_impl(
564  LockT& lock, const std::chrono::time_point<std::chrono::steady_clock, DurationT>& atime) noexcept {
565  if (std::chrono::steady_clock::now() >= atime) {
566  return std::cv_status::timeout;
567  }
568 
569  std::shared_ptr<SharedState> state = shared_state_;
570  std::unique_lock internal_lock(state->mtx);
571  lock.unlock();
572 
573  struct UnlockGuard final {
574  LockT& lock_ref;
575 
576  ~UnlockGuard() noexcept {
577  try {
578  lock_ref.lock();
579  } catch (std::exception&) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
580  }
581  }
582  } guard{lock};
583 
584  return state->cv.wait_until(internal_lock, atime);
585 }
586 
587 /**
588  * @typedef condition_variable
589  * @brief Snake-case alias for @c ConditionVariable.
590  */
592 
593 /**
594  * @typedef condition_variable_any
595  * @brief Snake-case alias for @c ConditionVariableAny.
596  */
598 
599 } // namespace vlink
600 
601 #else
602 
603 namespace vlink {
604 
609 
610 } // namespace vlink
611 
612 #endif
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VLINK_EXPORT
Definition: macros.h:81