CPP中CAS std::chrono 信号量与Any类的手动实现

news2025/6/1 6:41:43

前言

CAS(Compare and Swap) 是一种用于多线程同步的原子指令。它通过比较和交换操作来确保数据的一致性和线程安全性。CAS操作涉及三个操作数:内存位置V、预期值E和新值U。当且仅当内存位置V的值与预期值E相等时,CAS才会将内存位置V的值更新为新值U

C++中的CAS实现
在C++中,CAS操作可以通过std::atomic库中的compare_exchange_weakcompare_exchange_strong方法实现。这两个方法都用于比较和交换原子对象的值,但它们在失败时的行为有所不同

顺带提一下标准库实现的延时操作std::chrono

1.原子操作

我们平时直接进行的数据修改一般都是非原子操作,如果多个线程同时以非原子操作的方式修改同一个对象可能会发生数据争用,从而导致未定义行为;而原子操作能够保证多个线程顺序访问,不会导致数据争用,其执行时没有任何其它线程能够修改相同的原子对象。C++中可以使用std::atomic来定义原子变量。
CAS

常见计数器用法:

std::atomic<int> counter(0);
// 线程1增加计数器
counter.fetch_add(1);
// 线程2减少计数器
counter.fetch_sub(1);

常见控制标志用法:

std::atomic<bool> flag(true);
// 线程1检查标志
if (flag.load()) {
    // 执行操作
}
// 线程2修改标志
flag.store(false);

复杂数据类型用法:

#include <atomic>
#include <iostream>
#include <type_traits>
// 自定义类型 Point
struct Point {
    int x;
    int y;
    // 默认构造函数
    Point() : x(0), y(0) {}
    // 自定义构造函数
    Point(int x, int y) : x(x), y(y) {}
    // 拷贝构造函数和拷贝赋值运算符
    Point(const Point&) = default;
    Point& operator=(const Point&) = default;
    // 析构函数
    ~Point() = default;
};
int main() {
    static_assert(std::is_trivially_copyable<Point>::value, "Point must be trivially copyable");
    std::atomic<Point> atomic_point;
    Point p1(1, 2);
    atomic_point.store(p1);
    Point p2 = atomic_point.load();
    std::cout << "Atomic Point: (" << p2.x << ", " << p2.y << ")" << std::endl;
    return 0;
}

2. std::chrono

std::chrono是C++11引入的一个全新的有关时间处理的库。

新标准以前的C++往往会使用定义在ctime头文件中的C-Style时间库std::time。

相较于旧的库,std::chrono完善地定义了时间段(duration)、时钟(clock)和时间点(time point)三个概念,并且给出了对多种时间单位的支持,提供了更高的计时精度、更友好的单位处理以及更方便的算术操作(以及更好的类型安全)。

下面,我们将逐步说明std::chrono用法。

chrono库概念与相关用法
时间段(duration)
时间段被定义为std::chrono::duration,表示一段时间。

它的签名如下:

template<
    class Rep,
    class Period = std::ratio<1>
> class duration;

Rep是一个算术类型,表示tick数的类型,笔者一般会将其定义为int或者long long等整数类型,当然浮点数类型也是可行的。

Period代表tick的计数周期,它具有一个默认值——以一秒为周期,即 1 tick/s 。单位需要自行指定的情况会在后面涉及,这里暂时不讨论。

简单来说,我们可以认为一个未指定Period的duration是一个以秒为单位的时间段。

一个简单的例子:

#include <chrono>
#include <thread>
#include <iostream>
int main()
{
    std::chrono::duration<int> dur(2);
    std::cout << std::chrono::time_point_cast<std::chrono::seconds>
                (std::chrono::steady_clock::now())
                .time_since_epoch().count() << std::endl; // 以秒为单位输出当前时间
    std::this_thread::sleep_for(dur);
    std::cout << std::chrono::time_point_cast<std::chrono::seconds>
                (std::chrono::steady_clock::now())
                .time_since_epoch().count() << std::endl; // 以秒为单位输出当前时间
    return 0;
}

这段代码的作用是输出当前时间,随后睡眠两秒,再输出当前时间。dur描述了一个2秒的时间间隔。

duration支持几乎所有的算术运算。通俗地说,你可以对两个duration做加减运算,也可以对某个duration做数乘运算。

当然他也可以直接用于线程延时中
如下:

std::this_thread::sleep_for(std::chrono::seconds(2));

3.信号量

信号量的核心概念
头文件在C++20中是并发库技术规范(Technical Specification, TS)的一部分。信号量是同步原语,帮助控制多线程程序中对共享资源的访问。头文件提供了标准C++方式来使用信号量。
作用:

  • 通过计数器限制对共享资源的并发访问数量。
  • 实现线程间的同步(如生产者-消费者模型)。

类型:

  • 计数信号量(std::counting_semaphore):允许指定资源的最大并发数。
  • 二元信号量(std::binary_semaphore):计数为 1 的特殊信号量(类似于互斥锁)。

std提供的信号量如下:

#include <semaphore.h>

// 用于读写线程之间的通信
sem_t rwsem;

// 初始化读写线程通信用的信号量
sem_init(&rwsem, 0, 0);
sem_wait(&rwsem); // 等待信号量,子线程处理完注册消息会通知
sem_destroy(&rwsem);

在非c++20的情况下使用信号量需要自己实现,实现如下:
信号量的简单实现与使用
Semaphore.h文件

//实现一个信号量类
class Semaphore
{
public:
	Semaphore(int limit = 0)
		:resLimit_(limit)
	{}
	~Semaphore() = default;

	//获取一个信号量资源
	void wait()
	{
		std::unique_lock<std::mutex> lock(mtx_);
		//等待信号量有资源,没有资源的话,会阻塞当前线程
		cond_.wait(lock, [&]()->bool {return resLimit_ > 0; });
		resLimit_--;
	}

	//增加一个信号量资源
	void post()
	{
		std::unique_lock<std::mutex> lock(mtx_);
		resLimit_++;
		cond_.notify_all();
	}
private:
	int resLimit_;
	std::mutex mtx_;
	std::condition_variable cond_;
};

显然上述cond_.wait(lock, [&]()->bool {return resLimit_ > 0; });
处的条件决定了是计数信号量还是二元信号量

Result.h文件

//实现接收提交到线程池的task任务执行完成后的返回值类型Result
class Result {
public:
	Result(std::shared_ptr<Task> task, bool isValid = true);
	~Result() = default;
	//问题一:setva1方法,获取任务执行完的返回值的
	void setVal(Any any);
	
	//问题二:get方法,用户调用这个方法获取task的返回值
	Any get();
private:
	Any any_;//存储任务的返回值
	Semaphore sem_;//线程通信信号量
	std::shared_ptr<Task> task_;//指向对应获取返回值的任务对象
	std::atomic_bool isValid_;//返回值是否有效

};

Result.cpp文件

//Result方法的实现
Result::Result(std::shared_ptr<Task> task, bool isValid)
	:isValid_(isValid)
	,task_(task)
{
	task_->setResult(this);
}

Any Result::get()//用户调用
{
	if (!isValid_)
	{
		return "";
	}
	sem_.wait();	//task任务如果没有执行完,这里会阻塞用户的线程
	return std::move(any_);
}

void Result::setVal(Any any)//谁调用呢
{
	//存储task的返回值
	this->any_ = std::move(any);
	sem_.post();//已经获取的任务的返回值,增加信号量资源
}

4. Any类

C++17的三剑客分别是std::optional, std::any, std::vairant

4.1 Any类介绍

在日常编程中,我们可能会遇到这么一个场景:需要一个类型可以接收任意类型的变量,并且在需要使用该变量的时候还能恰当的进行转换。不难想到,C语言中的万能指针void可以满足我们上述的需求。但void的使用相对繁琐,且难免会涉及到大量的内存管理操作,这无疑加大了我们编程的复杂度。而在C++17中,any类的出现很好的解决了我们上述的问题。

std::any 是 C++17 引入的一个标准库类型,用于表示一个可以存储任意类型数据的容器。与 std::variant 不同,std::any 不限制存储的类型,因此它可以用来存储任意的对象。它的设计目标是提供一种简单的方式来存储和检索任意类型的值,而不需要像 void* 那样手动管理类型信息。

std::any 的基本特性
任意类型的存储:std::any 可以存储任何可拷贝构造的类型。
类型安全:std::any 提供了类型安全的访问,确保在访问值时不会发生类型错误。
动态类型:std::any 可以在运行时存储不同类型的对象,而无需在编译时指定类型。

下面是手动实现的简陋版Any类

//Any类型:可以接收任意数据的类型
class Any
{
public:
	Any() = default; 
	~Any() = default; 
	Any(const Any&) = delete; 
	Any& operator=(const Any&) = delete; 
	Any(Any&&) = default; 
	Any& operator=(Any&&) = default;

	template<typename T>
	Any(T data) :base_(std::make_unique<Derive<T>>(data))
	{}

	//这个方法能把any对象中存的数据提取出来
	template<typename T>	//T:int		Derive<int>
	T cast_()
	{
		//我们怎么从base_中找到它所指向的Derive对象,从他里面取出data对象
		//基类指针=》派生类指针	RTTI
		Derive<T>* pd = dynamic_cast<Derive<T>>(base_.get();	//使用智能指针的get方法获取裸指针
		if (pd == nullptr)
		{
			throw "type is unmatch!";
		}
		return pd->data_;
	}
private:
	//基类类型
	class Base
	{
	public:
		virtual ~Base() = default;
	};

	//派生类类型
	template<typename T>
	class Derive :public Base
	{
	public:
		Derive(T data) : data_(data)
		{

		}
		T data_;	//保存了任意的其他类型
	};

private:
	//定义一个基类的指针
	std::unique_ptr<Base> base_;
};

4.2 Any类实现细节分析

4.2.1 基类取用派生类成员

首先明确一点,在C++中,基类指针不能直接访问其所指向派生类的特有成员,这是面向对象编程中类型安全的重要规则。
所以在要取用所存储数据时需要对base_指针进行向下转型
Derive<T>* pd = dynamic_cast<Derive<T>>(base_.get();
当然也可以使用另一种方法,即借用虚函数

class Base {
public:
  virtual void execute() = 0; // 纯虚函数接口
};

class Derived : public Base {
public:
  void execute() override { 
    special(); // 通过多态间接调用
  }
  void special() {} // 派生类实现
};

Base* ptr = new Derived();
ptr->execute(); // 实际调用Derived::execute()

4.2.2 隐式模板构造函数

使用隐式模板构造函数来免去指明数据类型

template<typename T>
Any(T data) : base_(std::make_unique<Derive<T>>(data)) {}

构造函数是模板函数,能根据传入的data自动推导类型T
例如 Any a(10); 编译器自动推导 T = int

4.2.3 类型擦除设计

类型擦除(Type Erasure)是一种设计模式,用来隐藏对象的具体类型,统一暴露抽象接口,提供“运行时多态”。

通过基类指针 unique_ptr 指向模板派生类 Derive
基类 Base 不含类型信息,实现类型擦除
在这里插入图片描述

4.2.4 派生类模板封装

template<typename T>
class Derive : public Base {
  T data_; // 实际存储的数据
};

每个不同类型的数据都会被封装到独立的 Derive<T>
用户无需感知具体存储类型

4.2.5 提取数据时需要指定类型的原因

	//这个方法能把any对象中存的数据提取出来
	template<typename T>	//T:int		Derive<int>
	T cast_()
	{
		//我们怎么从base_中找到它所指向的Derive对象,从他里面取出data对象
		//基类指针=》派生类指针	RTTI
		Derive<T>* pd = dynamic_cast<Derive<T>>(base_.get();	//使用智能指针的get方法获取裸指针
		if (pd == nullptr)
		{
			throw "type is unmatch!";
		}
		return pd->data_;
	}
Any test(10);

test.cast_<int>();

类型安全恢复

  • 必须通过dynamic_cast尝试将基类指针转为具体的 Derive<T>*
  • 需要明确的模板参数 T 来恢复原始类型

运行时类型检查

  • 如果实际存储类型与请求类型不匹配:
    Any a(std::string("test"));
    a.cast_<int>(); // 抛出异常
    
  • dynamic_cast 失败返回 nullptr 触发异常

关键技术亮点

RAII资源管理

  • 使用 unique_ptr 自动管理派生类对象生命周期

  • 默认移动操作支持容器存储:

    std::vector<Any> vec;
    vec.push_back(Any(42));        // 存int
    vec.push_back(Any("hello"));   // 存const char*
    

类型安全边界

  • 构造时隐式类型推导(安全)
  • 提取时显式类型声明(安全)
  • 运行时验证类型匹配(安全)

禁止拷贝的合理性

  • Any(const Any&) = delete;
  • 避免浅拷贝问题(派生类对象不可复制)
  • 移动操作保留以支持高效转移资源

这种模式实现了 “动态类型安全容器”:

  1. 存数据:利用模板构造函数+类型擦除 → 静态多态
  2. 取数据:通过dynamic_cast+RTTI → 动态类型检查
  3. 完美平衡了灵活性与安全性

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

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

相关文章

PHP生成pdf方法

1&#xff1a;第一种方法&#xff1a; 主要使用PHP的扩展 【 “spatie/browsershot”: “3.57”】 使用这个扩展生成PDF需要环境安装以下依赖 1.1&#xff1a;NPM【版本&#xff1a;9.2.0】 1.2&#xff1a;NODE【版本&#xff1a;v18.19.1】 1.3&#xff1a;puppeteer【npm in…

【Android笔记】记一次 CMake 构建 Filament Android 库的完整排错过程(安卓交叉编译、CMake、Ninja)

写在前面的话&#xff0c;为了保持Sceneform-EQR始终是采用最新的filament&#xff0c;每隔一段时间我都会编译filament&#xff0c;并根据新增内容完善Sceneform-EQR。 现由于更换电脑&#xff0c;环境需重新配置。简单记录下编译出错和解决方式。 Sceneform-EQR 是EQ对谷歌“…

C#中的BeginInvoke和EndInvoke:异步编程的双剑客

文章目录 引言1. BeginInvoke和EndInvoke的基本概念1.1 什么是BeginInvoke和EndInvoke1.2 重要概念解释 2. 委托中的BeginInvoke和EndInvoke2.1 BeginInvoke方法2.2 EndInvoke方法2.3 两者的关系 3. 使用方式与模式3.1 等待模式3.2 轮询模式3.3 等待句柄模式3.4 回调模式 4. 底…

告别延迟!modbus tcp转profine网关助力改造电厂改造升级

发电需求从未如此旺盛。无论您是为客户发电还是为自身运营发电&#xff0c;您都需要提高运营效率&#xff0c;并在资产老化、资源萎缩的情况下&#xff0c;紧跟不断变化的法规。如今&#xff0c;智能系统和技术能够帮助您实现运营转型&#xff0c;提高可视性并实现关键流程自动…

《软件工程》第 5 章 - 需求分析模型的表示

目录 5.1需求分析与验证 5.1.1 顺序图 5.1.2 通信图 5.1.3 状态图 5.1.4 扩充机制 5.2 需求分析的过程模型 5.3 需求优先级分析 5.3.1 确定需求项优先级 5.3.2 排定用例分析的优先顺序 5.4 用例分析 5.4.1 精化领域概念模型 5.4.2 设置分析类 5.4.3 构思分析类之间…

阿里云国际版香港轻量云服务器:CN2 GIA加持,征服海外网络的“速度与激情”!

阿里云国际版香港轻量云服务器&#xff1a;CN2 GIA加持&#xff0c;征服海外网络的“速度与激情”&#xff01; 面对全球化业务拓展对网络连接的严苛要求&#xff0c;阿里云国际版香港轻量云服务器正成为出海企业和开发者的新宠。其核心优势在于搭载了CN2 GIA&#xff08;Glob…

Qt6无法识别OpenCV(Windows端开发)

这段时间在Windows 10上进行Qt6的开发。结果在build过程中&#xff0c;出现了如下错误: 但实际上&#xff0c;我明明安装了OpenCV4.10.0, 并且也在CMakeLists.txt中加入了相关内容。 但是&#xff0c;注意自己的编译输出: [1/5 1.4/sec] Automatic MOC and UIC for target R…

二、网络安全常见编码及算法-(2)

该文章主要介绍古典密码和隐写常用的密码和编码&#xff0c;日常中很少见&#xff0c;主要用于ctf比赛和考试学习一、古典密码 1、古典密码概念概述 古典密码是密码学发展早期所使用的一系列加密技术&#xff0c;这些密码主要依靠手工操作或简单的机械装置来实现信息的加密和…

Windows系统安装MySQL Connector 使用C++ VS2022连接MySQL

1. 官网及版本 1.1. 网址 官方文档 - 安装编译构建&#xff1a; https://dev.mysql.com/doc/connector-cpp/9.3/en/ 官方文档 - 使用案例&#xff1a; https://dev.mysql.com/doc/dev/connector-cpp/latest/ 下载地址&#xff1a; https://dev.mysql.com/downloads/connector/…

D2000平台上Centos使用mmap函数遇到的陷阱

----------原创不易&#xff0c;欢迎点赞收藏。广交嵌入式开发的朋友&#xff0c;讨论技术和产品------------- 在飞腾D2000平台上&#xff0c;安装了麒麟linux系统&#xff0c;我写了个GPIO点灯的程序&#xff0c;在应用层利用mmap函数将内核空间映射到用户态&#xff0c;然后…

Elasticsearch索引机制与Lucene段合并策略深度解析

引言 在现代分布式搜索引擎Elasticsearch中&#xff0c;文档的索引、更新和删除操作不仅是用户交互的核心入口&#xff0c;更是底层存储架构设计的关键挑战。本文围绕以下核心链路展开&#xff1a; 文档生命周期管理&#xff1a;从客户端请求路由到分片定位&#xff0c;从内存…

整合Jdk17+Spring Boot3.2+Elasticsearch9.0+mybatis3.5.12的简单用法

Elasticsearch是一个基于Lucene的分布式搜索和分析引擎&#xff0c;广泛应用于全文搜索、日志分析等场景。结合Spring Boot可以快速构建强大的搜索应用。本文将介绍如何在Spring Boot项目中集成和使用Elasticsearch。 ES9.0.1目前支持的包只有 elasticsearch-rest-client/ …

Ubuntu从0到1搭建监控平台:本地部署到公网访问实战教程Cpolar穿透与Docker部署全过程

文章目录 前言1.关于Ward2.Docker部署3.简单使用ward4.安装cpolar内网穿透5. 配置ward公网地址6. 配置固定公网地址总结 前言 IT运维人员是否常为服务器管理系统的复杂操作所困扰&#xff1f;当海量性能指标图表与密集预警信号同时涌现时&#xff0c;这种信息过载往往让专业团…

vscode java debug terminal 中文乱码

现象 解决 快捷键 ctrl , 进入setting 配文件添加 "terminal.integrated.automationProfile.windows": {"path": "cmd","args": ["/k","chcp","65001"]}terminal 启动时&#xff0c;活动也改为 utf-…

3D PDF如何制作?SOLIDWORKS MBD模板定制技巧

SOLIDWORKS制作3D PDF模版 SOLIDWORKS MBD能够帮助工程师以清晰直观的方式描述产品尺寸信息。在3D PDF文件中&#xff0c;用户可以自由旋转和移动视图&#xff0c;方便查看模型的各个尺寸细节。 本文将带您一步步学习如何使用SOLIDWORKS MBD制作专业的3D PDF模板&#xff0c;…

Qt DateTimeEdit(时间⽇期的微调框)

使⽤ QDateEdit 作为⽇期的微调框. 使⽤ QTimeEdit 作为时间的微调框 使⽤ QDateTimeEdit 作为时间⽇期的微调框. 这⼏个控件⽤法⾮常相似, 我们以 QDateTimeEdit 为例进⾏介绍. QDateTimeEdit 核⼼属性 属性说明dateTime时间⽇期的值. 形如 2000/1/1 0:00:00date单纯⽇期…

C# 类和继承(屏蔽基类的成员)

屏蔽基类的成员 虽然派生类不能删除它继承的任何成员&#xff0c;但可以用与基类成员名称相同的成员来屏蔽&#xff08;mask&#xff09; 基类成员。这是继承的主要功能之一&#xff0c;非常实用。 例如&#xff0c;我们要继承包含某个特殊方法的基类。该方法虽然适合声明它的…

基于vue框架的动物园饲养管理系统a7s60(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;饲养员,健康登记,工作进度,动物信息,进食信息,动物健康,动物医治,饲料信息,工作留言 开题报告内容 基于Vue框架的动物园饲养管理系统开题报告 一、研究背景与意义 &#xff08;一&#xff09;研究背景 随着城市化进程加快和公众对生…

WPS自动换行

换行前 换行后 快捷键 第一步&#xff1a;启用「自动换行」功能 选中目标单元格/区域&#xff1a;点击需要设置的单元格&#xff08;或拖动选中多个单元格&#xff09;。开启自动换行&#xff08;3种方式任选&#xff09;&#xff1a; 快捷按钮&#xff1a;在顶部菜单栏点击「…

maven中的grpc编译插件protobuf-maven-plugin详解

protobuf-maven-plugin 是 Maven 中用于编译 Protocol Buffers&#xff08;protobuf&#xff09;文件并生成对应语言代码&#xff08;如 Java、C、Python 等&#xff09;的插件。在 gRPC 项目中&#xff0c;它常被用来生成服务端和客户端所需的代码。以下是该插件的详细解析&am…