在等待 std::condition_variable 时如何处理系统时钟的变化?

How do I deal with the system clock changing while waiting on a std::condition_variable?

我正在尝试用 C++11 实现一些跨平台代码。此代码的一部分使用 std::condition_variable. When I need to do a timed wait on the semaphore, I use wait_until 或 wait_for 实现信号量对象。

我遇到的问题是 condition_variable 在基于 POSIX 的系统上的标准实现似乎依赖于 on the system clock, rather than the monotonic clock (see also: this issue against the POSIX spec)

这意味着如果系统时钟更改为过去的某个时间,我的条件变量将阻塞的时间比我预期的要长得多。例如,如果我希望我的 condition_variable 在 1 秒后超时,如果有人在等待期间将时钟调回 10 分钟,则 condition_variable 会阻塞 10 分钟 + 1 秒。我已经确认这是 Ubuntu 14.04 LTS 系统上的行为。

我需要依靠这个超时至少在一定程度上是准确的(即,它可能在一定误差范围内不准确,但如果系统时钟发生变化仍然需要执行)。看来我需要做的是编写我自己的 condition_variable 版本,它使用 POSIX 函数并使用单调时钟实现相同的接口。

听起来工作量很大 - 而且有点乱。是否有其他方法可以解决此问题?

这可能不是最好的解决方案,也不是很好的解决方案,但你说的是 "work around" 而不是 "a lot of work",所以:

  • 使用您的发行版设施来监控系统时钟的变化(我不太确定这些设施是什么;最坏的情况是,您可以 运行 每 5 分钟执行一次 cron 作业来检查时钟是否正常它的期望值)。
  • 在检测到系统时钟更改后,将某些内容传达给您拥有 waiting/sleeping 线程的进程。您可能会使用一个信号;或管道;或 unix 域套接字;甚至一些共享内存。
  • 在进程方面,确保你收到这个(即写一个信号处理程序,或者有一个线程在管道上阻塞I/O;或者使用非[=10=轮询共享内存] 睡眠-分别)
  • 收到有关系统时钟更改的通知后,调整您的进程,唤醒休眠线程,并根据更改后的时间重新评估需要做什么。也许它和以前完全一样,在这种情况下,您只需让您的线程再次使用条件变量即可。

不是很优雅,涉及大量开销 - 但这确实有道理,而且不是一些疯狂的技巧。

集中注册您的活动条件变量。

努力检测时钟错误,即使它是当前时钟上的线程自旋锁定 (ick) 或其他方式。

检测到时钟错误时,戳条件变量。

现在将您的条件变量包装在一个薄包装器中,该包装器还支持检测时钟滑动。它调用 wait_until 但用检测时钟滑动的谓词替换谓词,当这种情况发生时,等待就会中断。

当你的实现被破坏时,你必须做你必须做的。

在考虑了这个问题的可能解决方案之后,似乎最有意义的一个是禁止使用 std::condition_variable(或者至少明确说明它将始终使用系统时钟)。然后我必须基本上重新实现标准库的 condition_variable 自己,以尊重时钟选择的方式。

由于我必须支持多个平台(Bionic、POSIX、Windows,最终是 MacOS),这意味着我将维护此代码的多个版本。

虽然这很糟糕,但似乎其他选择甚至更糟糕。

我遇到了同样的问题。我的一位同事给了我一个提示,让我改用 <pthread.h> 中的某些 C 函数,它对我来说非常有效。

举个例子,我有:

std::mutex m_dataAccessMutex;
std::condition_variable m_dataAvailableCondition;

标准用法:

std::unique_lock<std::mutex> _(m_dataAccessMutex);
// ...
m_dataAvailableCondition.notify_all();
// ...
m_dataAvailableCondition.wait_for(...); 

以上可以用pthread_mutex_tpthread_cond_t代替。优点是你可以指定时钟是单调的。简要用法示例:

#include <pthread.h>

// Declare the necessary variables
pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_condattr_t m_attr;
pthread_cond_t m_cond;

// Set clock to monotonic
pthread_condattr_init(&m_attr);
pthread_condattr_setclock(&m_attr, CLOCK_MONOTONIC);
pthread_cond_init(&m_cond, &m_attr); 

// Wait on data
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
ts.tv_sec += timout_in_seconds; 
pthread_mutex_lock(&m_mutex);
int rc = pthread_cond_timedwait(&m_cond, &m_mutex, &ts);
if (rc != ETIMEDOUT)
    ; // do things with data
else 
    ; // error: timeout
// ...
pthread_mutex_unlock(&m_mutex); // have to do it manually to unlock
// ...

// Notify the data is ready
pthread_cond_broadcast(&m_cond);