sem_timedwait_C/C++ 修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分析
修改系统时间,导致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存在的缺陷!!
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_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 <= 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, 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;
}
测试结果:
[root@lincoding sem]# ./sem_test
signaled:0 Wait time:1000ms
signaled:0 SemTimedWait time:2000ms
总结
尽量不要使用sem_timedwait函数来实现延时等待的功能,若要使用该延时等待的功能,建议使用sem_trywait+usleep 实现的延时阻塞!
sem_timedwait_C/C++ 修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分析相关推荐
- 修改系统时间导致myeclipse不能自动发布的解决方法
今天为了测试开发的系统时间,在myeclipse打开的情况下修改了系统时间,当我测试完毕,再把系统时间改回来后,问题出现了,不管怎么改程序,myeclipse主是不能自动将修改后的文件发布到tomca ...
- sem_timedwait 和修改系统时间
sem_timedwait 和修改系统时间 对于int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);传入 ...
- 安卓应用修改系统时间
博客: 安卓之家 掘金: jp1017 微博: 追风917 CSDN: 蒋朋的家 简书: 追风917 前言 通常,我们来获取系统时间来做一些事情,那么,如何设置系统时间呢? 问题抛出来,解决方法也就应 ...
- win7怎么修改锁定计算机时间,电脑时间改不了,教您电脑无法修改系统时间如何解决...
默认情况下,win7操作系统时间是默认设置好的,但是有时候时间乱了,想要修改,却发现win7操作系统无法修改电脑时间,怎么回事呢?出现这样的情况是由于系统中管理员将其禁止导致的,下面,小编就来跟大家探 ...
- Linux服务器修改系统时间
Linux服务器在某些情况下可能会导致系统时间与当前时间不一致的情况,比如说操作系统之后,或者长时间断电之后,或者多次重启之后,或者是上一次设置时间时没有将其写入系统中,导致重启后时间失效的. 下面就 ...
- Electron/NodeJS修改系统时间
在用Electron做PC软件,需要同步服务器时间. 避免因为本地设置时间异常导致上传上来的数据异常 方案已更新,废弃node-windows,采用child-process 前因 毕竟Electro ...
- linux下如何修改系统时间
我们一般使用"date -s"命令来修改系统时间.比如将系统时间设定成2018年2月23日的命令如下. #date -s 02/23/2018 将系统时间设定成下午11点12分0秒 ...
- VB 禁止修改系统时间
VB 禁止修改系统时间 添加到网络收藏宣传得积分可免费下载本站所有资源 VB 禁止修改系统时间 当任何程序或用户修改系统时间的时候,系统会将 WM_TIMECHANGE ...
- linux 修改时间的命令,Linux 常用命令(查看版本、修改系统时间)
1.查看内核版本:cat /proc/version [root@gjxb default]# cat /proc/version Linux version 2.6.9-78.8AXS2smp (p ...
最新文章
- 转:MFC中常用类,宏,函数介绍
- 系统设计经典题:手把手教你搭建一个IM(即时通讯) 系统
- 分布式系统关注点:无状态
- 简述冯诺依曼工作原理_深入浅出讲解计算机原理
- 浅析VS2010反汇编 VS 反汇编方法及常用汇编指令介绍 VS2015使用技巧 调试-反汇编 查看C语言代码对应的汇编代码...
- ConcurrentHashMap的源码分析-resizeStamp
- 【Android】关于参数的传递问题
- ajax context this,如何使ajax里的this指向不改变
- hive 修改cluster by算法_Hive入门实战(一)概述与使用
- 《深入剖析Tomcat》源码
- 使用Magoshare for Mac无法打开恢复的文件或扫描后找不到丢失的文件怎么办?
- arcgis10.2之Maplex(高级标注扩展模块)
- 《图像处理、分析与机器视觉 第四版》数学形态学基本概念——学习笔记
- CF1611E1 Escape The Maze (easy version)+ CF1611E2 Escape The Maze (hard version)
- MySQL 8.0 Command Line Client 不能打开或者闪退
- 分享一个铁死亡数据库
- 怎么用python画圆柱_python绘制圆柱体
- 用Simplify3D联机打印时会重启
- 开源社区活跃度分析——api.github.com的使用
- 基于python+mysql超市信息管理系统(附完整源代码)
热门文章
- excel自动排班有假期_Excel假期晚餐时间表
- 西北工业大学计算机专硕分数线,西北工业大学电子信息(专硕)专业考研录取分数线-研究生分数线-历年分数线...
- 关于贫穷和拖延的天才发现
- Tvori推出2.0版本,让VR动画制作更简单
- 经验总结 | R语言整理数据常用小技巧
- 06-mysql自定义函数
- 蓝桥杯 手机尾号评分
- 最有效的期货趋势策略:期货反向跟单
- 调节pycharm字体大_钢管钢板大字符喷码机手持式@钢管钢板大字符喷码机手持式最大字符...
- 【ARM-Linux开发】linux下代码调试