C/C++ 修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分
修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分析介绍最近修复项目问题时,发现当系统时间往前修改后,会导致 sem_timedwait存在的缺陷的理由: 假设当前系统时间是 sem_timedwait函数介绍int sem_timedwait(sem_t *sem,const struct timespec *abs_timeout);
第二个参数 struct timespec { time_t tv_sec; /* 秒 */ long tv_nsec; /* 纳秒 */ }; 解决方法可以通过 sem_trywait函数介绍函数 int sem_trywait(sem_t *sem) 执行成功返回0,执行失败返回 -1且信号量的值保持不变。 sem_trywait + usleep的方式实现主要实现的思路: #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 = 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 <= timeoutUs. delayUs = timeoutUs - elapsedUs; // 睡眠时间取最小的值 timeWait = std::min( delayUs,timeWait ); // 进行睡眠 单位是微秒 ret = usleep( timeWait ); if( ret != 0 ) { return false; } // 睡眠延时时间双倍自增 timeWait *= 2; // 睡眠延时时间不能超过最大值 timeWait = std::min( timeWait,maxTimeWait ); // 计算开始时间到现在的运行时间 单位是微秒 elapsedUs = GetMonnotonicTime() - startUs; } while( elapsedUs <= timeoutUs ); // 如果当前循环的时间超过预设延时时间则退出循环 // 超时退出,则返回false return false; } // 获取需要延时等待时间的绝对时间戳 inline timespec* GetAbsTime( size_t milliseconds,timespec& absTime ) { // CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,// 中间时刻如果系统时间被用户改成其他,则对应的时间相应改变 clock_gettime( CLOCK_REALTIME,&absTime ); absTime.tv_sec += milliseconds / 1000; absTime.tv_nsec += (milliseconds % 1000) * 1000000; // 纳秒进位秒 if( absTime.tv_nsec >= 1000000000 ) { absTime.tv_sec += 1; absTime.tv_nsec -= 1000000000; } return &absTime; } // sem_timedwait 实现的睡眠 -- 存在缺陷 // 如果信号量大于0,则减少信号量并立马返回true // 如果信号量小于0,则阻塞等待,当阻塞超时时返回false bool SemTimedWait( size_t timeout ) { timespec absTime; // 获取需要延时等待时间的绝对时间戳 GetAbsTime( timeout,absTime ); if( sem_timedwait( &g_sem,&absTime ) != 0 ) { return false; } return true; } int main(void) { bool signaled = false; uint64_t startUs = 0; uint64_t elapsedUs = 0; // 初始化信号量,数量为0 sem_init( &g_sem,0 ); ////////////////////// sem_trywait+usleep 实现的睡眠 //////////////////// // 获取开始的时间,单位是微秒 startUs = GetMonnotonicTime(); // 延时等待 signaled = Wait(1000); // 获取超时等待的时间,单位是微秒 elapsedUs = GetMonnotonicTime() - startUs; // 输出 signaled:0 Wait time:1000ms std::cout << "signaled:" << signaled << "t Wait time:" << elapsedUs/1000 << "ms" << std::endl; ////////////////////// sem_timedwait 实现的睡眠 //////////////////// ///////////////////// 存在缺陷,原因当在sem_timedwait阻塞中时,修改了系统时间,则会导致sem_timedwait一直阻塞 ////////////////// // 获取开始的时间,单位是微秒 startUs = GetMonnotonicTime(); // 延时等待 signaled = SemTimedWait(2000); // 获取超时等待的时间,单位是微秒 elapsedUs = GetMonnotonicTime() - startUs; // 输出 signaled:0 SemTimedWait time:2000ms std::cout << "signaled:" << signaled << "t SemTimedWait time:" << elapsedUs/1000 << "ms" << std::endl; return 0; } 测试结果: [[email?protected] sem]# ./sem_test signaled:0 Wait time:1000ms signaled:0 SemTimedWait time:2000ms 总结尽量不要使用 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |