考虑以下源代码,它完全符合POSIX:
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
int main (int argc, char ** argv) {
pthread_cond_t c;
pthread_mutex_t m;
char printTime[UCHAR_MAX];
pthread_mutex_init(&m, NULL);
pthread_cond_init(&c, NULL);
for (;;) {
struct tm * tm;
struct timeval tv;
struct timespec ts;
gettimeofday(&tv, NULL);
printf("sleep (%ld)\n", (long)tv.tv_sec);
sleep(3);
tm = gmtime(&tv.tv_sec);
strftime(printTime, UCHAR_MAX, "%Y-%m-%d %H:%M:%S", tm);
printf("%s (%ld)\n", printTime, (long)tv.tv_sec);
ts.tv_sec = tv.tv_sec + 5;
ts.tv_nsec = tv.tv_usec * 1000;
pthread_mutex_lock(&m);
pthread_cond_timedwait(&c, &m, &ts);
pthread_mutex_unlock(&m);
}
return 0;
}
每5秒打印一次当前系统日期,然而,在获取当前系统时间(gettimeofday
)和条件等待(pthread_cond_timedwait
)之间,它会休眠3秒。
在打印“睡眠(...)”之后,尝试将系统时钟设置为过去两天。怎么了?好吧,pthread_cond_timedwait
现在等待两天和2秒,而不是像通常那样等待2秒钟。
我该如何解决? 如何编写POSIX兼容代码,当用户操作系统时钟时不会中断?
请记住,即使没有用户交互,系统时钟也可能会发生变化(例如,NTP客户端可能每天自动更新一次时钟)。将时钟设置为未来是没有问题的,它只会导致睡眠提前唤醒,这通常没有问题,您可以轻松地“检测”并相应地处理,但将时钟设置为过去(例如,因为它是在未来运行,NTP检测到并修复它可能会导致一个大问题。
PS:
我的系统上既不存在pthread_condattr_setclock()
也不存在CLOCK_MONOTONIC
。这些对于POSIX 2008规范(“Base”的一部分)是必需的,但是大多数系统仍然只遵循截至今天的POSIX 2004规范,并且在POSIX 2004规范中这两个是可选的(高级实时扩展)。
答案 0 :(得分:4)
有趣的是,我之前没有遇到过这种行为,但是,我再也不习惯用我的系统时间捣乱: - )
假设你出于正当理由这样做,一个可能的(虽然是kludgy)解决方案是让另一个线程的唯一目的是定期启动条件变量以唤醒受影响的任何线程。
换句话说,比如:
while (1) {
sleep (10);
pthread_cond_signal (&condVar);
}
等待条件变量被踢的代码应该检查它的谓词(以处理虚假的唤醒),这样就不会对功能产生任何实际的不利影响。
这是一个轻微的性能打击,但每十秒一次不应该是一个问题。这只是为了照顾你的等待时间等待很长时间的情况(无论出于何种原因)。
另一种可能性是重新设计您的应用程序,以便您根本不需要定时等待。
在线程由于某种原因需要被唤醒的情况下,它总是由另一个线程完成,它完全能够踢一个条件变量来唤醒一个(或者广播以唤醒它们)。
这与我上面提到的踢腿线程非常相似,但更多的是作为你的架构的一个组成部分,而不是一个螺栓固定。
答案 1 :(得分:3)
您可以针对此问题保护代码。一种简单的方法是使用一个线程,其唯一目的是观察系统时钟。保留条件变量的全局链接列表,如果时钟观察器线程看到系统时钟跳转,它会广播列表中的每个条件变量。然后,您只需将pthread_cond_init
和pthread_cond_destroy
包含在向全局链表添加/删除条件变量的代码中。使用互斥锁保护链接列表。