微信公众号:「小林coding」 用简洁的方式,分享编程小知识。
修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分析 介绍最近修复项目问题时,发现当系统时间往前修改后,会导致sem_timedwait
函数一直阻塞。通过搜索了发现int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
传入的第二个阻塞时间参数是绝对的时间戳,那么该函数是存在缺陷的。
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
存在的缺陷!!
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; /* 纳秒 */
};
解决方法
可以通过sem_trywait
+ usleep
的方式来实现与sem_timedwait
函数的类似功能,并且不会发生因系统时间往前改而出现一直阻塞的问题。
函数 sem_trywait()
和sem_wait()
有一点不同,即如果信号量的当前值为0,则返回错误而不是阻塞调用。错误值errno设置为EAGAIN。sem_trywait()
其实是sem_wait()
的非阻塞版本。
int sem_trywait(sem_t *sem)
执行成功返回0,执行失败返回 -1且信号量的值保持不变。
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(curTime->tv_sec) * factor;
}
// 获取自系统启动的调单递增的时间 -- 转换单位为微秒
uint64_t GetMonnotonicTime()
{
timespec curTime;
uint64_t result = GetTimeConvSeconds( &curTime, 1000000 );
result += static_cast(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 = 10000; // 最大的睡眠的时间为10000微秒,也就是10毫秒
size_t timeWait = 1; // 睡眠时间,默认为1微秒
size_t delayUs = 0; // 剩余需要延时睡眠时间
const uint64_t startUs = GetMonnotonicTime(); // 循环前的开始时间,单位微秒
uint64_t elapsedUs = 0; // 过期时间,单位微秒
int ret = 0;
do
{
// 如果信号量大于0,则减少信号量并立马返回true
if( sem_trywait( &g_sem ) == 0 )
{
return true;
}
// 系统信号则立马返回false
if( errno != EAGAIN )
{
return false;
}
// delayUs一定是大于等于0的,因为do-while的条件是elapsedUs
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?