CPP中CAS std::chrono 信號量與Any類的手動實現

前言

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對象//基類指針=》派生類指針	RTTIDerive<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對象//基類指針=》派生類指針	RTTIDerive<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.pswp.cn/bicheng/82930.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82930.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/82930.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Axure設計案例——科技感對比柱狀圖

想讓數據對比展示擺脫平淡無奇&#xff0c;瞬間抓住觀眾的眼球嗎&#xff1f;那就來看看這個Axure設計的科技感對比柱狀圖案例&#xff01;科技感設計風格運用獨特元素打破傳統對比柱狀圖的常規&#xff0c;營造出一種極具沖擊力的視覺氛圍。每一組柱狀體都仿佛是科技戰場上的士…

怒更一波免費聲音克隆和AI配音功能

寶子們&#xff01; 最近咱軟件TransDuck的免費聲音克隆和AI配音功能被大家用爆啦&#xff01;感謝各位自來水瘋狂安利&#xff01;&#xff01; DD這里也是收到好多用戶提的寶貴建議&#xff01;所以&#xff0c;連夜肝了波更新&#xff01; 這次重點更新使用克隆音色進行A…

UDP協議原理與Java編程實戰:無連接通信的奧秘

1.UDP協議核心原理 1. 無連接特性&#xff1a;快速通信的基石 UDP&#xff08;User Datagram Protocol&#xff0c;用戶數據報協議&#xff09;是TCP/IP協議族中無連接的輕量級傳輸層協議。與TCP的“三次握手”建立連接不同&#xff0c;UDP通信無需提前建立鏈路&#xff0c;發送…

vue-seamless-scroll 結束從頭開始,加延時后滾動

今天遇到一個大屏需求&#xff1a; 1??初始進入頁面停留5秒&#xff0c;然后開始滾動 2??最后一條數據出現在最后一行時候暫停5秒&#xff0c;然后返回1?? 依次循環&#xff0c;發現vue-seamless-scroll的方法 ScrollEnd是監測最后一條數據消失在第一行才回調&#xff…

[Protobuf] 快速上手:安全高效的序列化指南

標題&#xff1a;[Protobuf] (1)快速上手 水墨不寫bug 文章目錄 一、什么是protobuf&#xff1f;二、protobuf的特點三、使用protobuf的過程&#xff1f;1、定義消息格式&#xff08;.proto文件&#xff09;(1)指定語法版本(2)package 聲明符 2、使用protoc編譯器生成代碼&…

uniapp調用java接口 跨域問題

前言 之前在Windows10本地 調試一個舊項目&#xff0c;手機移動端用的是Uni-app&#xff0c;vue的版本是v2。后端是java spring-boot。運行手機移動端的首頁請求后臺接口老是提示錯誤信息。 錯誤信息如下&#xff1a; Access to XMLHttpRequest at http://localhost:8080/api/…

[ Qt ] | Qlabel使用

目錄 屬性 setTextFormat 插入圖片 設置圖片根據窗口大小實時變化 邊框和對其方式 ?編輯 設置縮進 設置伙伴 Qlabel可以用來顯式圖片和文字 屬性 text textFormat Qlabel獨有的機制&#xff1a;buddy setTextFormat 插入圖片 設置圖片根據窗口大小實時變化 Qt中表…

Springboot 項目一啟動就獲取HttpSession

在 Spring Boot 項目中&#xff0c;HttpSession 是有狀態的&#xff0c;通常只有在用戶發起 HTTP 請求并建立會話后才會創建。因此&#xff0c;在項目啟動時&#xff08;即應用剛啟動還未處理任何請求&#xff09;是無法獲取到 HttpSession 的。 方法一&#xff1a;使用 HttpS…

Step9—Ambari Web UI 初始化安裝 (Ambari3.0.0)

Ambari Web UI 安裝 如果還不會系統性的部署&#xff0c;或者前置內容不熟悉&#xff0c;建議從Step1 開始閱讀。不通版本針對于不同操作系統可能存在差異&#xff01;這里我也整理好了 https://doc.janettr.com/install/manual/ 1. 進入 Ambari Web UI 并登錄 在瀏覽器中訪…

熱門大型語言模型(LLM)應用開發框架

我們來深入探索這些強大的大型語言模型&#xff08;LLM&#xff09;應用開發框架&#xff0c;并且我會嘗試用文本形式描述一些核心的流程圖&#xff0c;幫助您更好地理解它們的工作機制。由于我無法直接生成圖片&#xff0c;我會用文字清晰地描述流程圖的各個步驟和連接。 Lang…

機器學習數據降維方法

1.數據類型 2.如何選擇降維方法進行數據降維 3.線性降維&#xff1a;主成分分析&#xff08;PCA&#xff09;、線性判別分析&#xff08;LDA&#xff09; 4.非線性降維 5.基于特征選擇的降維 6.基于神經網絡的降維 數據降維是將高維數據轉換為低維表示的過程&#xff0c;旨在保…

太陽系運行模擬程序-html動畫

太陽系運行模擬程序-html動畫 by AI: <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>交互式太陽系…

2025年全國青少年信息素養大賽 scratch圖形化編程挑戰賽 小低組初賽 內部集訓模擬題解析

2025年信息素養大賽初賽scratch模擬題解析 博主推薦 所有考級比賽學習相關資料合集【推薦收藏】 scratch資料 Scratch3.0系列視頻課程資料零基礎學習scratch3.0【入門教學 免費】零基礎學習scratch3.0【視頻教程 114節 免費】 歷屆藍橋杯scratch國賽真題解析歷屆藍橋杯scr…

grid網格布局

使用flex布局的痛點 如果使用justify-content: space-between;讓子元素兩端對齊&#xff0c;自動分配中間間距&#xff0c;假設一行4個&#xff0c;如果每一行都是4的倍數那沒任何問題&#xff0c;但如果最后一行是2、3個的時候就會出現下面的狀況&#xff1a; /* flex布局 兩…

通義靈碼2.5——基于MCP實現我的12306火車票智能查詢小助手

本文因排版顯示問題&#xff0c;為保證閱讀體驗&#xff0c;請大家訪問&#xff1a; 通義靈碼2.5——基于MCP打造我的12306火車票智能查詢小助手-CSDN博客 前沿技術應用全景圖 本項目作為通義靈碼2.5的標桿實踐案例&#xff0c;展現了AI輔助開發在復雜業務系統中的革命性突破…

Unity Button 交互動畫

在UGUI的Button組件中&#xff0c;有一個過渡動畫表現的功能。可以對按鈕的不同交互狀態添加交互反饋動畫&#xff0c;來提高玩家的交互體驗。 交互狀態 名稱 描述 Normal 正常情況 Highlighted 高亮顯示&#xff0c;例如鼠標觸碰到按鈕點擊范圍 Pressed 按鈕被按下的時…

釘釘熱點實時推送助理-思路篇

以下是針對熱點實時推送助理的功能描述&#xff0c;結合機器學習技術棧與用戶場景的通俗化解釋&#xff1a; 快速體驗的話直接用釘釘掃描下方二維碼體驗 1. 核心功能 &#xff08;1&#xff09;熱點抓取引擎 類比&#xff1a;像蜘蛛爬取全網信息&#xff08;網絡爬蟲信息抽取…

remote: error: hook declined to update refs/heads.....

gitee拉取分支&#xff0c;修改上傳出現的問題&#xff0c;折騰了好久&#xff0c;淺淺記錄. 1. 首次克隆倉庫 # 克隆倉庫&#xff08;使用 HTTPS 或 SSH&#xff09; git clone ------------ cd xxx-project2. 配置正確的用戶信息&#xff08;關鍵步驟&#xff01;&#xff…

使用Vue + Element Plus實現可多行編輯的分頁表格

需求背景&#xff1a; 在現代前端開發中&#xff0c;表格作為數據展示和交互的重要組件&#xff0c;在各類管理系統、數據平臺中有著廣泛的應用。隨著用戶對數據操作便捷性要求的不斷提高&#xff0c;具備靈活編輯功能的表格成為了開發中的常見需求。特別是在需求處理大…

奧威BI+AI——高效智能數據分析工具,引領數據分析新時代

隨著數據量的激增&#xff0c;企業對高效、智能的數據分析工具——奧威BIAI的需求日益迫切。奧威BIAI&#xff0c;作為一款顛覆性的數據分析工具&#xff0c;憑借其獨特功能&#xff0c;正在引領數據分析領域的新紀元。 一、?零報表環境下的極致體驗? 奧威BIAI突破傳統報表限…