C语言:修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分析

一、网络摘文

参考目录: https://zhuanlan.zhihu.com/p/83581365

1. 背景介绍及原因分析

最近修复项目问题时,发现当系统时间往前修改后,会导致sem_timedwait函数一直阻塞。通过搜索发现

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

传入的第二个阻塞时间参数是绝对的时间戳,那么该函数是存在缺陷的。

(1)sem_timedwait存在的缺陷的理由

假设当前系统时间是1565000000(2019-08-05 18:13:20),sem_timedwait传入的阻塞等待的时间戳是1565000100(2019-08-05 18:15:00),那么sem_timedwait就需要阻塞1分40秒(100秒),若在sem_timedwait阻塞过程中,中途将系统时间往前修改成1500000000(2017-07-14 10:40:00),那么sem_timedwait此时就会阻塞2年多! 这就是sem_timedwait存在的缺陷!!

(2)sem_timedwait函数介绍

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
  • 如果信号量大于0,则对信号量进行递减操作并立马返回正常
  • 如果信号量小于0,则阻塞等待,当阻塞超时时返回失败(errno 设置为 ETIMEDOUT)

第二个参数abs_timeout 参数指向一个指定绝对超时时刻的结构,这个结果由自 Epoch,1970-01-01 00:00:00 +0000(UTC)秒数和纳秒数构成。这个结构定义如下:

struct timespec {
   
    time_t tv_sec;        /* 秒 */
    long   tv_nsec;       /* 纳秒 */
};

2. 解决方法

可以通过 sem_trywait+ usleep 的方式来实现与 sem_timedwait 函数的类似功能,并且不会发生因系统时间往前改而出现一直阻塞的问题,下面来介绍一下sem_trywait函数。

(1)sem_trywait函数介绍

函数 sem_trywait() 和 sem_wait() 有一点不同,即如果信号量的当前值为0,则返回错误而不是阻塞调用。错误值errno设置为EAGAIN。sem_trywait()其实是sem_wait()的非阻塞版本

int sem_trywait(sem_t *sem)

执行成功返回0,执行失败返回 -1且信号量的值保持不变。

(2)sem_trywait + usleep的方式实现

主要实现的思路:

sem_trywait 函数不管信号量为0或不为0都会立刻返回。

  • 当函数正常返回的时候就不usleep;
  • 当函数不正常返回时就通过usleep来实现延时;

具体实现方式如下代码中的 bool Wait( size_t timeout ) 函数:

#include 
#include

#include
#include 

sem_t g_sem;

// 获取自系统启动的调单递增的时间
inline uint64_t GetTimeConvSeconds( timespec* curTime, uint32_t factor )
{
   
    // CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
    clock_gettime( CLOCK_MONOTONIC, curTime );
    return static_cast<uint64_t>(curTime->tv_sec) * factor;
}

// 获取自系统启动的调单递增的时间 -- 转换单位为微秒
uint64_t GetMonnotonicTime()
{
   
    timespec curTime;
    uint64_t result = GetTimeConvSeconds( &curTime, 1000000 );
    result += static_cast<uint32_t>(curTime.tv_nsec) / 1000;
    return result;
}

// sem_trywait + usleep的方式实现
// 如果信号量大于0,则减少信号量并立马返回true
// 如果信号量小于0,则阻塞等待,当阻塞超时时返回false
bool Wait( size_t timeout )
{
   
    const size_t timeoutUs = timeout * 1000; // 延时时间由毫米转换为微秒
    const size_t maxTimeWait 

你可能感兴趣的:(C语言,#,Tina,调试经验总结,sem_timedwait,阻塞,sem_trywait,usleep)