一、网络摘文
参考目录: 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 <string>
#include<iostream>
#include<semaphore.h>
#include <time.h>
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