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 <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, 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 实现的延时阻塞!
C/C++ 修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分析的更多相关文章
- KingbaseES V8R3 由于修改系统时间导致sys_rman备份故障案例
案例说明: 此案例,为复现"current time may be rewound"错误.对于数据库环境,在使用前必须保证系统时间的正确性.如果数据库创建后,再将系统时间修改为 ...
- Wince修改系统时间问题
当我们需要修改到系统时间的时候,需要用到下面四个函数:SetLoaclTime,GetLocalTime,SetSystemTime,GetSystemTime.这四个函数是用来修改或者 ...
- linux下修改系统时间
一.查看时间: [root@localhost ~]# date2016年 11月 19日 星期六 12:46:37 CST 二.修改时间,修改系统时间 [root@localhost ~]# dat ...
- Linux下修改系统时间并写入BIOS
我们一般使用“date -s”命令来修改系统时间.比如将系统时间设定成2005年7月26日的命令如下. #date -s 07/26/2005 将系统时间设定成下午11点12分0秒的命令如下. #da ...
- Linux怎样修改系统时间
修改linux的时间可以使用date指令 修改日期: 时间设定成2009年5月10日的命令如下: #date -s 05/10/2009 修改时间: 将系统时间设定成上午10点18分0秒的命令如下. ...
- linux修改系统时间date命令加clock -w
http://m.jb51.net/LINUXjishu/117784.html 修改linux系统时间的方法(date命令) 11-18 23:22:27作者:脚本之家 命令格式为: date -s ...
- CentOS修改系统时间
CentOS修改系统时间 操作: 1. date –s '1987-05-02 10:10:10' 2. clock –w //将日期写入CMOS 补充: 修改Linux时间一般涉及到3个命令: 1. ...
- C#技术点--修改系统时间
C#的System.DateTime类提供了对日期时间的封装,用它进行时间的转换和处理很方便,但是我没有在其中找到任何可以用来修改系统时间的成员.用过VC.VB等的朋友可能知道,我们可以调用Win32 ...
- ubuntu16.4下使用QT修改系统时间
我也是在网上找的,自己随便改了一下六个lineEdit控件,每个控件输入日期时间,点击按钮触发函数可修改时间. 1 //一键修改系统时间 QString year = ui->lineEdit_ ...
随机推荐
- 基于 Github 存储附件的 wordpress 插件
前言 插件名称 wp-github-gos, 本插件核心功能使用了 GitHub API 设置页面和核心业务逻辑主要参考插件 wordpress-qcloud-cos 的实现,替换了其中 腾讯云 CO ...
- java实现线程的两种方式
要求:一个火车站,三个售票员卖出100张票 1.通过继承Thread类实现 实现思路:建立ThreadTest类继承Thread并建立三个线程卖100张票 public class ThreadTes ...
- javascript获取指定区间范围随机数
//获取指定区间范围随机数,包括lowerValue和upperValuefunction randomFrom(lowerValue,upperValue){ return Math.floo ...
- 这 3 个 Set 集合的实现有点简单,那来做个总结吧
Set 接口是 Java Collections Framework 中的一员,它的特点是:不能包含重复的元素,允许且最多只有一个 null 元素.Java 中有三个常用的 Set 实现类: Hash ...
- Linux内核OOM killer机制
程序运行了一段时间,有个进程挂掉了,正常情况下进程不会主动挂掉,简单分析后认为可能是运行时某段时间内存占用过大,系统内存不足导致触发了Linux操作系统OOM killer机制,将运行中的进程杀掉了. ...
- JavaScript数据结构——集合的实现与应用
与数学中的集合概念类似,集合由一组无序的元素组成,且集合中的每个元素都是唯一存在的.可以回顾一下中学数学中集合的概念,我们这里所要定义的集合也具有空集(即集合的内容为空).交集.并集.差集.子集的特性 ...
- SCI论文的时态
如果有的杂志对时态有要求,则以下所述都没有用了. 有些杂志也会专门有些比较“特别”的要求,比如Cell,要求Abstract全部使用一般现在时. 英语谓语动词时态共有16种,在英文科技论文中用得较为频 ...
- java学习笔记(中级篇)—java实现高质量图片压缩
使用java几十行代码实现一个高质量图片压缩程序,再也不用去自己找网络的压缩程序啦!而且很多网上的工具还有水印或者其他的限制,自己动手写一个简单的应用,是再合适不过了. 一.实现原理 1.声明两个字符 ...
- 后端基于方法的权限控制--Spirng-Security
后端基于方法的权限控制--Spirng-Security 默认情况下, Spring Security 并不启用方法级的安全管控. 启用方法级的管控后, 可以针对不同的方法通过注解设置不同的访问条件: ...
- IBM实习工作(二)
2019年秋招前夕再次到ibm项目组参加实习两周,这次主要负责的需求是建立牛奶数据池,在二级菜单建立对账单数据池,数据由Excel导入生成. 分析整个需求,主要分为以下几块: 1.牛奶数据池前台页面, ...
