找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5192

积分

0

好友

704

主题
发表于 昨天 21:43 | 查看: 5| 回复: 0

经常看到类似下面这样的条件变量初始化代码:

pthread_condattr_t cattr;
pthread_condattr_init(&cattr);
pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
pthread_mutex_init(&trigger_mutex, NULL);
pthread_cond_init(&trigger_cond, &cattr);
pthread_condattr_destroy(&cattr);

凭什么要多写这三行 condattr_initsetclockcondattr_destroy?直接用 pthread_cond_init(&cond, NULL) 难道不香吗?其实这背后暗藏着一个容易被忽视的坑,绕过去,你的程序在关键时刻可能就会犯病。

两种时钟的区别

Linux 提供了两种行为截然不同的常用时钟:

  • CLOCK_REALTIME:系统挂钟时间,也就是你敲 date 命令看到的那个。它是可变的,NTP 网络校时或者手动执行 date -s 都可以修改它。
  • CLOCK_MONOTONIC:单调递增时钟,从系统启动那一刻开始计时。它不可变,不受任何外部时间调整影响,只会一直往前走。

用示意图理解会更直观:

CLOCK_REALTIME:  2026-04-29 14:30:00  →  2026-04-29 14:30:01  →  2026-04-29 14:25:00 (NTP回拨!)
CLOCK_MONOTONIC: 0s  →  1s  →  2s  →  3s  (永远递增)

pthread_cond_timedwait 工作时,需要拿你传入的 abstime 和一个时钟做比较才能判断是否已经超时。至于用哪个时钟来比较,则完全由条件变量初始化时的 setclock 决定,而它的默认值正是 CLOCK_REALTIME

CLOCK_REALTIME 会闹出什么幺蛾子

pthread_cond_timedwait 要求你传递一个绝对时间。假如你想等待 5 秒,通常会这样写:

struct timespec abstime;
clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += 5;
pthread_cond_timedwait(&cond, &mutex, &abstime);

你计算出的 abstime 实际是“当前墙钟时间 + 5秒”。一旦等待期间系统时间被人动了手脚,场面就会失控。

NTP 校时回拨导致永远等不到超时

想象这样一个场景:

T+0s:  线程计算 abstime = 14:30:05 (当前14:30:00 + 5秒)
T+1s:  NTP同步,系统时间回拨到 14:25:00
       此时 abstime 14:30:05 还在"未来5分钟"
       线程继续等,5秒超时变成了5分钟

这在物联网设备上几乎是个必然事件。设备上电后第一件事往往就是联网校时,如果恰好有线程卡在 timedwait,校时完成的一瞬间,就是 bug 亮剑的时刻。

手动调时导致立刻超时

还有一个反向操作:

T+0s:  线程计算 abstime = 14:30:05
T+1s:  有人手动把系统时间调到 14:31:00
       abstime 14:30:05 已经过去了
       pthread_cond_timedwait 立刻返回 ETIMEDOUT

开发调试时手动改时间不算罕见,但生产环境里某些嵌入式设备也可能因 RTC 电池掉电、网络校时失败等原因出现时间跳变。

夏令时切换

一些地区仍需切换夏令时,切换瞬间系统时间会跳 1 小时。长达 1 小时的超时偏差,足以让看门狗的喂狗逻辑当场失效。

CLOCK_MONOTONIC 如何彻底解决

CLOCK_MONOTONIC 最大的好处就是不受外界干扰,只往前走。用这个时钟计算出的 abstime 是“系统启动后 X 秒 + 5 秒”,这个时间点绝不会因为 NTP 或手动调时而改变。

T+0s:  线程计算 abstime = monotonic_now + 5s = 1005s
T+1s:  NTP同步,系统墙钟回拨
       但 monotonic 时钟不受影响,继续走到 1001s
       线程等到 1005s 超时,精确5秒

时钟属性必须配套

这才是最容易一脚踩空的深坑——条件变量的时钟属性和 clock_gettime 使用的时钟必须保持严格一致。

错误写法:条件变量走默认的 CLOCK_REALTIME,获取时间却用 CLOCK_MONOTONIC

// 条件变量默认时钟是 CLOCK_REALTIME
pthread_cond_init(&cond, NULL);

// 但获取时间用的是 CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &abstime);
abstime.tv_sec += 5;

// 内部拿 abstime 和 CLOCK_REALTIME 比较
// 时间基准不同,超时行为不可预测
pthread_cond_timedwait(&cond, &mutex, &abstime);

CLOCK_MONOTONIC 的时间值(比如系统启动后 100 秒)和 CLOCK_REALTIME 的时间值(比如 Unix 纪元后 1773000000 秒)根本不在一个数量级上。拿 CLOCK_MONOTONIC 的时间值去和 CLOCK_REALTIME 比较,要么立刻就超时,要么你就等到天荒地老吧。

正确写法:在初始化条件变量时就明确指定 CLOCK_MONOTONIC,获取时间也步调一致:

pthread_condattr_t cattr;
pthread_condattr_init(&cattr);
pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
pthread_cond_init(&cond, &cattr);
pthread_condattr_destroy(&cattr);

// 获取时间也用 CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &abstime);
abstime.tv_sec += 5;
pthread_cond_timedwait(&cond, &mutex, &abstime);

一种具体的封装方案

我们可以将条件变量的初始化与超时等待整合到一个结构里,从根源上杜绝时钟不匹配的问题。在 云栈社区 的技术实践中,这类底层同步原语的封装思路常被用来提升服务稳定性。

typedef struct {
    pthread_mutex_t mutex;
    pthread_cond_t  cond;
    int             ready;
} event_t;

int event_init(event_t *ev)
{
    pthread_mutex_init(&ev->mutex, NULL);

    pthread_condattr_t cattr;
    pthread_condattr_init(&cattr);
    pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
    pthread_cond_init(&ev->cond, &cattr);
    pthread_condattr_destroy(&cattr);

    ev->ready = 0;
    return 0;
}

int event_wait(event_t *ev, uint32_t timeout_ms)
{
    struct timespec abstime;
    clock_gettime(CLOCK_MONOTONIC, &abstime);

    abstime.tv_sec  += timeout_ms / 1000;
    abstime.tv_nsec += (timeout_ms % 1000) * 1000000L;
    if (abstime.tv_nsec >= 1000000000L) {
        abstime.tv_sec  += 1;
        abstime.tv_nsec -= 1000000000L;
    }

    pthread_mutex_lock(&ev->mutex);
    while (ev->ready == 0) {
        int ret = pthread_cond_timedwait(&ev->cond, &ev->mutex, &abstime);
        if (ret == ETIMEDOUT) {
            pthread_mutex_unlock(&ev->mutex);
            return -1;
        }
    }
    ev->ready = 0;
    pthread_mutex_unlock(&ev->mutex);
    return 0;
}

void event_signal(event_t *ev)
{
    pthread_mutex_lock(&ev->mutex);
    ev->ready = 1;
    pthread_cond_signal(&ev->cond);
    pthread_mutex_unlock(&ev->mutex);
}

这套封装在处理 高并发 场景下的条件同步时,能让代码意图更清晰,也彻底规避了因时钟混用导致的诡异超时问题。




上一篇:STM32嵌入式开发路线图:从CubeMX到FreeRTOS,实用开源项目盘点
下一篇:C语言如何实现多态?手动vtable模拟类虚函数机制
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-22 03:36 , Processed in 0.671455 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表