【随笔记】C++ condition_variable 陷阱

news2024/11/26 19:56:21

问题说明

通过 std::condition_variable 来实现超时等待,会受到系统时间变化的影响,系统时间倒退修改就会导致延后唤醒,系统时间提前将会导致提前被唤醒,返回结果仍为超时。

这种问题只有在系统时间发生变化的时候才会出现,例如搭配 NTP 更新功能,硬件还未同步时间时,一般在 1993 年,此时使用了 wait_for() 这类接口等待 10 秒,结果在 10 秒内被 ntp 同步更新了时间到 2023,那么时间生效的一瞬间,wait_for() 就会直接被唤醒,且返回的结果是超时唤醒。

另外一种时间倒退的场景,则影响会更大,例如在 2023 年,时间调回了 2022 年,那么 wait_for() 将会等待一年多才会被超时唤醒,代码的表现现象则是该线程出现了 wait() 的效果。

通过分析 std::condition_variable 源码,可以很清晰看到使用的是系统时间:
在这里插入图片描述在这里插入图片描述

示例代码:

实现一个可以随时被打断的延时等待类。

有隐患的代码

bool DelayControl::delay(unsigned int millisecond)
{
	bool is_timeout	= false;

	unique_lock< mutex > lock(mutex_data_);
	is_runing_ = true;
	is_timeout = (cv_status::timeout == cond_.wait_for(lock, chrono::milliseconds(millisecond)));
	is_runing_ = false;
	lock.unlock();
	
	return is_timeout;
}
void DelayControl::stop()
{
	unique_lock< mutex > lock(mutex_data_);
	cond_.notify_all();
}

改进方案一(使用 select 方式实现):缺点是一个对象会浪费两个文件描述符资源

DelayControl::DelayControl()
{
	is_runing_ = false;
	pipe(pipefd_);
}

bool DelayControl::delay(unsigned int millisecond)
{
	int result;
	fd_set rdfs;
    struct timeval timeout;
    bool is_timeout = false;
	
	is_runing_ = true;
	FD_ZERO(&rdfs);
	FD_SET(pipefd_[0], &rdfs);
	timeout.tv_sec = millisecond / 1000;
	timeout.tv_usec = (millisecond - ((millisecond / 1000) * 1000)) * 1000;
	switch((result = select(pipefd_[1] + 1, &rdfs, NULL, NULL, &timeout))){
		case 0: is_timeout = true; break;
	}
	is_runing_ = false;
	return is_timeout;
}

void DelayControl::stop()
{
	write(pipefd_[1], "", 1);
}

改进方案二(使用 pthread_cond_timedwait 方式实现):完美方案

关键在于使用了 CLOCK_MONTONIC ,其用不是系统时间,而是内核的计数器 jiffies,系统每次启动时,jiffies初始化为0。每来一个timer interrupt,jiffies加1,即它代表系统启动后流逝的tick数,jiffies 只会单调递增。

DelayControl::DelayControl()
{
	is_runing_ = false;
	pthread_condattr_init(&cond_cattr_);
	pthread_mutex_init(&mutex_data_, NULL);
	pthread_condattr_setclock(&cond_cattr_, CLOCK_MONOTONIC);
	pthread_cond_init(&cond_, &cond_cattr_);
}

DelayControl::~DelayControl()
{
	pthread_mutex_lock(&mutex_data_);
	pthread_cond_broadcast(&cond_);
	pthread_mutex_unlock(&mutex_data_);
	pthread_cond_destroy(&cond_);
	pthread_mutex_destroy(&mutex_data_);
}

bool DelayControl::delay(unsigned int millisecond)
{
	struct timespec tv;
	bool is_timeout = false;
	
	pthread_mutex_lock(&mutex_data_);
	is_runing_ = true;
	clock_gettime(CLOCK_MONOTONIC, &tv);
	millisecond += (tv.tv_sec * 1000) + (tv.tv_nsec / 1000000);
	tv.tv_sec = millisecond / 1000;
	tv.tv_nsec = (millisecond - ((millisecond / 1000) * 1000)) * 1000 * 1000;
	is_timeout = pthread_cond_timedwait(&cond_, &mutex_data_, &tv) ? true : false;
	is_runing_ = false;
	pthread_mutex_unlock(&mutex_data_);
	
	return is_timeout;
}

bool DelayControl::isRuning()
{
	bool is_runing = false;
	pthread_mutex_lock(&mutex_data_);
	is_runing = is_runing_;
	pthread_mutex_unlock(&mutex_data_);	
	return is_runing;
}

void DelayControl::stop()
{
	pthread_mutex_lock(&mutex_data_);
	is_runing_ = false;
	pthread_cond_broadcast(&cond_);
	pthread_mutex_unlock(&mutex_data_);
}

用如下随机设置系统时间的方式压力测 6 小时通过:

#define RAND(_MIN_, _MAX_) (rand() % (_MAX_-_MIN_+1) + _MIN_)
int main()
{
	Logger::getInstance().init("/mnt/UDISK/pre_bullying/logs/DelayControl.log", 1024*1024*2, 1);
	
	std::shared_ptr<MeasureTime> sp_timer_;
	std::shared_ptr<DelayControl> sp_delay_;
	
	sp_delay_ = std::make_shared<DelayControl>();
	sp_timer_ = std::make_shared<MeasureTime>(100);
	srand((unsigned)time(NULL)); 
	
	{
		DelayControl mDelayControl;
		mDelayControl.delay(1000);
	}
	
	std::thread t([&]{
		char buf[64] = {0};
		while(true){
			usleep(RAND(0, 5000) * 1000);
			system("ntpclient -s -c 1 -h ntp7.aliyun.com -i 3");
			usleep(RAND(0, 5000) * 1000);
			snprintf(buf, sizeof(buf), "date -s \"%.4d-%.2d-%.2d %.2d:%.2d:%.2d\"",  RAND(1990, 2030), RAND(1, 12), RAND(1, 29), RAND(0, 23), RAND(1, 60), RAND(1, 60));
			iprint("set time:[%s]", buf);
			system(buf);
		}
	});
	t.detach();
	
	while(true)
	{
		int delay = RAND(0, 5000);
		unsigned long long ms = 0;
		iprint("delay:-->[%d]", delay);
		sp_timer_->update();
		bool isdone = sp_delay_->delay(delay);
		ms = sp_timer_->getMillisecond();
		iprint("delay %s:[%d][%d][%lld]", delay != ms ? "delay != ms" : "done", isdone, delay, sp_timer_->getMillisecond());
	}
	
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1053176.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatisPlus(七)等值查询

等值查询 条件查询&#xff1a;使用 Wrapper 对象&#xff0c;传递查询条件。 QueryWrapper&#xff08;不要使用&#xff09; 代码 Testvoid eq() {QueryWrapper<User> wrapper new QueryWrapper<>();wrapper.eq("name", "张三");List<…

装饰器模式详解和实现(设计模式 二)

装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你动态地将对象添加到现有对象中&#xff0c;以提供额外的功能&#xff0c;同时又不影响其他对象。 实现示例 1.定义一个接口或抽象类&#xff0c;表示被装饰对象的公共接口 //抽…

CSS 滚动驱动动画 view-timeline-inset

view-timeline-inset 语法例子&#x1f330; 正 scroll-padding 为正正的 length正的 percentage 负 scroll-padding 为负负的 length负的 percentage 兼容性 view-timeline-inset 在使用 view() 时说过, 元素在滚动容器的可见性推动了 view progress timeline 的进展. 默认…

数据结构—快速排序(续)

引言&#xff1a;在上一篇中我们详细介绍了快速排序和改进&#xff0c;并给出了其中的一种实现方式-挖坑法 但其实快速排序有多种实现方式&#xff0c;这篇文章再来介绍其中的另外两种-左右指针法和前后指针法。有了上一篇挖坑法的启示&#xff0c;下面的两种实现会容易许多。 …

面试记录_

1&#xff1a;面试杉岩数据&#xff08;python开发&#xff09; 1.1.1 选择题 for(int i0;i<n;i){for(int j0;j<n;jji) } }O(n) * (O(0) O(n/1) O(n/2) O(n/3) ... O(n/n)) 在最坏情况下&#xff0c;内部循环的迭代次数为 n/1 n/2 n/3 ... n/n&#xff0c;这是…

电脑找不到vcruntime140_1.dll丢失的解决方法-一键修复教程

vcruntime140_1.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C Redistributable的一部分。这个库文件包含了一些运行时函数&#xff0c;用于支持各种软件程序的正常运行。当一个程序需要调用这些函数时&#xff0c;它会通过加载vcruntime140_1.dll文件来实现。因…

MySQL基础进阶

文章目录 MySQL基础进阶 约束 \color{red}{约束} 约束约束的概念和分类约束的概念约束的分类 非空约束概念语法 唯一约束概念语法 主键约束概念语法 数据库设计 \color{red}{数据库设计} 数据库设计软件的研发步骤数据库设计概念数据库设计的步骤表关系一对一一对多&#xff08…

Vue3父子组件数据传递

getCurrentInstance方法 Vue2中&#xff0c;可以通过this来获取当前组件实例&#xff1b; Vue3中&#xff0c;在setup中无法通过this获取组件实例&#xff0c;console.log(this)打印出来的值是undefined。 在Vue3中&#xff0c;getCurrentInstance()可以用来获取当前组件实例…

el-menu 导航栏学习(1)

最简单的导航栏学习跳转实例效果&#xff1a; &#xff08;1&#xff09;index.js路由配置&#xff1a; import Vue from vue import Router from vue-router import NavMenuDemo from /components/NavMenuDemo import test1 from /components/test1 import test2 from /c…

1200*B. Sorted Adjacent Differences(构造)

Problem - 1339B - Codeforces 解析&#xff1a; 题目要求每相邻两个值差的绝对值相等或递增。 先排序&#xff0c;可以想到我们先取两侧的数肯定相距最远&#xff0c;然后靠中心每次取两个数&#xff0c;这样符合题目要求。 直接遍历&#xff0c;先取的是答案靠后的数据&…

基于微信小程序的校园快递代取系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言用户微信小程序端的主要功能有&#xff1a;配送员微信小程序端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获…

python爬取沈阳市所有肯德基餐厅位置信息

# 爬取沈阳所有肯德基餐厅位置信息 import requests import json import reurl http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?opkeyword headers {User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0…

Ipa Guard使用手册

使用手册 开始使用ipa guard代码混淆界面介绍文件混淆-界面介绍安装和登录Ipa Guard 相关教程 下载安装Ipa Guardipaguard注册和登录 下载安装Ipa Guard 可以前往ipaguard工具官网下载&#xff0c;工具是免费下载&#xff0c;免费体验使用的。下载地址是https://www.ipaguard.…

关于工作中爬取网站的一些思路记录

声明&#xff1a;只是因为工作中需要&#xff0c;且基本不会对别人的网站构成什么不好的影响&#xff0c;做个思路记录&#xff01;&#xff01;&#xff01; 尊重网站所有者、控制请求频率、遵守网站规则、尊重个人隐私 平常工作中难免会遇到需要爬取别人网站的需求&#xff0…

华为云云耀云服务器L实例评测 | 实例评测使用之硬件性能评测:华为云云耀云服务器下的硬件运行评测

华为云云耀云服务器L实例评测 &#xff5c; 实例评测使用之硬件性能评测&#xff1a;华为云云耀云服务器下的硬件运行评测 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀…

linux系统中wifi移植方法

第一&#xff1a;移植wifi现象 在linux系统的RK3399中空板上&#xff0c;确认rk3399中控板linux系统已经可以正常运行。本操作是在rk3399中控板上的WIFI模块&#xff0c;linux内核加载wifi驱动后&#xff0c;再配置上正确的wifi密码&#xff0c;就可以实现rk3399中控板通过wifi…

LeNet网络复现

文章目录 1. LeNet历史背景1.1 早期神经网络的挑战1.2 LeNet的诞生背景 2. LeNet详细结构2.1 总览2.2 卷积层与其特点2.3 子采样层&#xff08;池化层&#xff09;2.4 全连接层2.5 输出层及激活函数 3. LeNet实战复现3.1 模型搭建model.py3.2 训练模型train.py3.3 测试模型test…

shopify目录层级释义

└── theme├── assets // assets目录包含主题中使用的所有资源文件&#xff0c;包括图像、CSS和JavaScript文件。├── config // config目录包含主题的配置文件。 配置文件在主题编辑器的主题设置区域中定义设置&#xff0c;并存储它们的值。├── layout // layou…

会议AISTATS(Artificial Intelligence and Statistics) Latex模板参考文献引用问题

前言 在看AISTATS2024模板的时候&#xff0c;发现模板里面根本没有教怎么引用&#xff0c;要被气死了。 如下&#xff0c;引用(Cheesman, 1985)的时候&#xff0c;模板是自己手打上去的&#xff1f;而且模板提供的那三个引用&#xff0c;根本也没有Cheesman这个人&#xff0c…

Mysql各种锁

一.不同存储引擎支持的锁机制 Mysql数据库有多种数据存储引擎&#xff0c;Mysql中不同的存储引擎支持不同的锁机制 MyISAM和MEMORY存储引擎采用的表级锁 InnoDB存储引擎支持行级锁&#xff0c;也支持表级锁&#xff0c;默认情况下采用行级锁 二.锁类型的划分 按照数据操作…