【C++11】異步編程

異步編程的概念

什么是異步?

  • 異步編程是一種編程范式,允許程序在等待某些操作時繼續執行其它任務,而不是阻塞或等待這些操作完成。

異步編程vs同步編程?

  • 在傳統的同步編程中,代碼按順序同步執行,每個操作需要等待前一個操作完成。這種方式在處理I/O操作、網絡請求、計算密集型任務時可能會導致程序的性能瓶頸。舉個例子,當程序需要從網上獲取數據時,需要等待數據返回后才能繼續執行,等待期間CPU可能處于空閑狀態,浪費了資源。
  • 而異步編程不同,它允許程序在操作未完成期間繼續執行其它任務。例如,程序可以發起一個網絡請求,然后繼續執行其它操作,等到網絡請求完成時,再處理獲取的數據。

異步編程的優點?

  • 提高性能,并發執行多個任務,更高效地利用CPU資源
  • 提高響應速度,程序在等待某些操作完成時繼續響應用戶輸入,提高用戶體驗

實現異步編程

? ? ? ? 在介紹異步編程工具之前,我們先來回答一個問題:

為什么主線程無法直接獲取新線程的計算結果?

內存隔離性:線程擁有獨立的棧空間,新線程中局部變量的生命周期僅限于該線程。來看一下下面這個例子:

int res; // 定義在主線程中
// 引用傳遞res
thread t([&res]() {res = 42;
});
t.join();
cout << res << endl; // 可以輸出42,但存在風險!

這種方式看似可行,實則存在嚴重問題:

  • 競態條件:如果沒有正確使用join,主線程可能會在t修改res執行前就讀取它
  • 懸掛引用:如果res是局部變量且線程未及時完成,新線程可能訪問已銷毀的內存。

同步缺失問題:及時使用全局變量或堆內存,仍需手動同步:

mutex mtx;
int* res = new int(0); // 在堆上創建變量
thread t([&mtx, res](){unique_lock<mutex> lock(mtx);*res = 42;
});
// 主線程需要輪詢檢查res,效率極低

核心機制:std::future

? ? ? ? std::future提供了一種標準化的線程間通信機制,其核心原理是共享狀態,當異步任務完成后,結果會被寫入共享狀態,future通過檢查該狀態安全地傳遞結果。? ? ? ??

????????std::future是一個模板類,用于表示異步操作的結果,允許開發者在未來的某個時刻查詢異步操作的狀態、等待操作完成或獲取操作結果。通常我們不直接創建future對象 ,而是與std::async、std::packaged_task或std::promise配合使用。

任務啟動器:std::async

std::async是一種將任務與std::future關聯的簡單方法,創建并運行一個異步任務,并返回一個與該任務結果關聯的std::future對象。async的任務是否同步運行取決于傳遞的參數:

  • std::launch::deferred:表明該函數會被延遲調用,知道future對象上調用get或wait方法才會開始執行任務
  • std::launch::async:表明函數會在創建的新線程上運行
  • std::launch::deferred|std::launch::async:內部由操作系統自動選擇策略

延遲調用:

#include <iostream>
#include <future>
#include <unistd.h>using std::cout;
using std::endl;int myadd(int num1, int num2)
{cout << "add start!" << endl;return num1 + num2;
}
int main()
{cout << "---------1----------" << endl;std::future<int> fut = std::async(std::launch::deferred, myadd, 1, 2);sleep(1);cout << "---------2----------" << endl;cout << fut.get() << endl;return 0;
}

執行結果:

不難發現,直到我們調用了get方法,才執行myadd函數

異步執行:

#include <iostream>
#include <future>
#include <unistd.h>using std::cout;
using std::endl;int myadd(int num1, int num2)
{cout << "add start!" << endl;return num1 + num2;
}
int main()
{cout << "---------1----------" << endl;std::future<int> fut = std::async(std::launch::async, myadd, 1, 2);sleep(1);cout << "---------2----------" << endl;cout << fut.get() << endl;return 0;
}

執行結果:

在調用之后創建的線程立即執行了myadd函數。

結果傳遞器:std::promise

????????std::promise是一個用于設置異步操作結果的機制。允許我們在一個線程中設置值或異常,然后再另一個線程中通過future對象檢索這些值或異常,通常與std::async、std::thread等結合使用,在異步操作中傳遞結果。

#include <iostream>
#include <thread>
#include <future>//通過在線程中對promise對象設置數據,其他線程中通過future獲取設置數據的方式實現獲取異步任務執行結果的功能
void Add(int num1, int num2, std::promise<int> &prom) {std::this_thread::sleep_for(std::chrono::seconds(3));prom.set_value(num1 + num2);return ;
}int main()
{std::promise<int> prom;std::future<int> fu = prom.get_future();std::thread thr(Add, 11, 22, std::ref(prom));int res = fu.get();std::cout << "sum: " << res << std::endl;thr.join();return 0;
}

注意事項

  • std::promise的生命周期:需要確保promise對象在future對象需要使用它的時候保持有效,一旦promise對象銷毀,任何嘗試通過future訪問其結果的操作都將失敗。
  • 線程安全:std::promise的set_value和set_exception方法是線程安全的,但仍應該避免在多個線程中同時調用它們,這意味著設計存在問題。
  • 將std::promise對象傳給線程函數時,通常使用std::move或std::ref來避免不必要的復制。、

任務封裝器:std::packaged_task

std::packaged_task是一個模板類,主要用于將一個可調用對象包裝起來,以便異步執行,并能夠獲取其返回結果。它和std::future、std::thread緊密相關,常用于多線程編程中。

使用std::packaged_task的流程:

  • 創建packaged_task對象:創建packaged_task對象需要傳遞一個可調用對象,將其封裝為異步任務。
  • 獲取future對象:使用get_future方法可以獲取與packaged_task關聯的future對象,用于獲取異步操作的結果。
  • 執行任務:通過operator()或調用thread在一個新線程中執行。注意packed_task對象是不能復制的,所以需要通過std::move或智能指針傳遞。
  • 獲取結果:主線程種調用future對象的get方法可以等待異步任務完成并獲取其返回值。如果任務尚未完成,get方法會阻塞直到結果可用。
#include <iostream>
#include <thread>
#include <future>
#include <memory>
//pakcaged_task的使用
//   pakcaged_task 是一個模板類,實例化的對象可以對一個函數進行二次封裝,
//pakcaged_task可以通過get_future獲取一個future對象,來獲取封裝的這個函數的異步執行結果int Add(int num1, int num2) {std::this_thread::sleep_for(std::chrono::seconds(3));return num1 + num2;
}int main()
{//std::packaged_task<int(int,int)> task(Add);//std::future<int> fu = task.get_future();//task(11, 22);  task可以當作一個可調用對象來調用執行任務//但是它又不能完全的當作一個函數來使用//std::async(std::launch::async, task, 11, 22);//std::thread thr(task, 11, 22);//但是我們可以把task定義成為一個指針,傳遞到線程中,然后進行解引用執行//但是如果單純指針指向一個對象,存在生命周期的問題,很有可能出現風險//思想就是在堆上new對象,用智能指針管理它的生命周期auto ptask = std::make_shared<std::packaged_task<int(int,int)>>(Add);std::future<int> fu = ptask->get_future();std::thread thr([ptask](){(*ptask)(11, 22);});int sum = fu.get();std::cout << sum << std::endl;thr.join();return 0;
}

三種異步工具的比較

std::async

  • 自動任務調度:async提供了一種簡單方便地方式來創建異步任務,只需要調用async,傳入函數和參數,就會自動執行異步任務,并返回future對象用于得到異步操作結果。
  • 靈活性有限:盡管簡單,但靈活性有限,無法完全控制任務的調度方式(如任務在哪個線程運行)
  • 適用場景:適用于簡單的異步任務,不需要復雜的任務調度和管理。

std::promise

  • 手動設置結果:promise是一種更底層的機制,允許手動設置異步操作的結果,并將結果傳遞給與之關聯的future對象,使用時需要將promise和異步任務的邏輯結合在一起。
  • 更多的代碼管理:使用promise需要手動管理任務的執行和結果的傳遞,因此比async更靈活和復雜。
  • 使用場景:適用于需要手動控制任務結果傳遞的場景,或異步任務的結果是由多個步驟或線程決定的。

std::packaged_task

  • 封裝可調用對象:packaged_task可以將一個可調用對象封裝起來,并通過future對象傳遞執行結果,這使它可以用于復雜的異步任務調度。
  • 與其他工具結合使用:packaged_task的設計使得它可以很容易地與std::thread、自定義線程池、任務隊列等結合使用,靈活地管理任務的執行。
  • 使用場景:適合需要高度靈活的任務管理、封裝任務并手動控制任務執行的場景,特別適用于實現自定義線程池。

異步線程池設計方案

線程池需要管理的數據:

  • 控制線程停止的變量:支持原子操作,保證關閉線程池操作的線程安全
  • 任務池:存放待執行的異步任務
  • 互斥鎖與條件變量:保證線程安全與同步
  • 一批工作線程:用于執行異步任務

線程池的實現思路:

  • 在啟動時預先創建一批工作線程,執行線程入口函數:不斷從任務池中取出任務進行執行,沒有任務則等待條件變量就緒
  • 用戶通過Push方法可以將要執行的任務傳入線程池,先將傳入的任務封裝為packaged_task異步任務后,通過packaged_task的get_future方法可以獲得future對象,然后將異步任務放入任務池,喚醒工作線程執行異步任務。
  • 將future對象返回給使用者,使用者可以通過get方法獲取異步任務的執行結果。
#include <vector>
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <memory>using namespace std;class ThreadPool
{
public:using Functor = function<void()>;ThreadPool(int threadNum = 1): _stop(false){// 創建一批執行線程入口函數的線程for(int i = 0; i < threadNum; i++){_threads.emplace_back(&ThreadPool::entry, this);}}// 萬能引用template<typename F, typename ...Args>auto Push(F&& func, Args&& ...args) -> future<decltype(func(args...))> // 編譯時推導返回值類型{using return_type = decltype(func(args...));// 完美轉發auto tmp_func = bind(forward<F>(func), forward<Args>(args)...);auto task = make_shared<packaged_task<return_type()>>(tmp_func);future<return_type> fut = task->get_future();{unique_lock<mutex> lock(_mtx);_tasks.push_back([task](){(*task)();});}_cv.notify_one();return fut;}~ThreadPool(){Stop();}void Stop(){if(_stop == true) return ;_stop = true;_cv.notify_all(); // 喚醒所有線程,進行回收for(auto& thread : _threads){if(thread.joinable()){thread.join(); // 回收線程}}}
private:// 不斷從任務池中取出任務執行void entry(){while(!_stop){vector<Functor> tmp_tasks;{unique_lock<mutex> lock(_mtx);_cv.wait(lock, [this](){return _stop || !_tasks.empty(); // 當線程池停止(要回收線程)或任務池有任務時喚醒線程});tmp_tasks.swap(_tasks);}for(auto& task : tmp_tasks){task();}}}
private:atomic<bool> _stop;vector<Functor> _tasks;vector<thread> _threads;mutex _mtx;condition_variable _cv;    
};int add(int a, int b)
{return a + b;
}
int main()
{ThreadPool pool;for(int i = 0; i < 10; i++){future<int> fut = pool.Push(add, 10, i);cout << fut.get() << endl;}return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/900090.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/900090.shtml
英文地址,請注明出處:http://en.pswp.cn/news/900090.shtml

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

相關文章

FastAPI與ASGI深度整合實戰指南

一、ASGI技術體系解析 1. ASGI協議棧全景圖 #mermaid-svg-a5XPEshAsf64SBkw {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-a5XPEshAsf64SBkw .error-icon{fill:#552222;}#mermaid-svg-a5XPEshAsf64SBkw .error-te…

數組與特殊壓縮矩陣

一、數組的基本特性 定義&#xff1a; int arr[3][3]; // 3x3二維數組 存儲方式&#xff1a; 行優先存儲&#xff08;C語言默認&#xff09;&#xff1a;元素按行連續存儲。 列優先存儲&#xff1a;需手動實現&#xff08;如科學計算中的Fortran風格&#xff09;。 訪問元素…

Word 插入無頁眉頁碼的空白頁(即插入奇數頁)

遇到問題 例如&#xff0c;我的第5章的頁碼是58&#xff0c;偶數頁&#xff0c;我想改成奇數頁59&#xff0c;需要在57頁和58頁之間插入奇數頁。 解決辦法 單擊上一頁&#xff08;57頁&#xff09;&#xff0c;打開“視圖-大綱”&#xff0c;找到要插入奇數頁的位置&#x…

OpenCV 從入門到精通(day_05)

1. 模板匹配 1.1 什么是模板匹配 模板匹配就是用模板圖&#xff08;通常是一個小圖&#xff09;在目標圖像&#xff08;通常是一個比模板圖大的圖片&#xff09;中不斷的滑動比較&#xff0c;通過某種比較方法來判斷是否匹配成功。 1.2 匹配方法 rescv2.matchTemplate(image, …

【目標檢測】【深度學習】【Pytorch版本】YOLOV3模型算法詳解

【目標檢測】【深度學習】【Pytorch版本】YOLOV3模型算法詳解 文章目錄 【目標檢測】【深度學習】【Pytorch版本】YOLOV3模型算法詳解前言YOLOV3的模型結構YOLOV3模型的基本執行流程YOLOV3模型的網絡參數 YOLOV3的核心思想前向傳播階段反向傳播階段 總結 前言 YOLOV3是由華盛頓…

LN2220 2A 高效率升壓 DC/DC 電壓調整器

1、產品概述 LN2220 是一款微小型、高效率、升壓型 DC/DC 調整器。 電路由電流模 PWM 控制環路&#xff0c;誤差放大器&#xff0c;斜波補償電路&#xff0c; 比較器和功率開關等模塊組成。該芯片可在較寬負載范圍內 高效穩定的工作&#xff0c;內置一個 4A 的功率開關和…

【大模型基礎_毛玉仁】6.3 知識檢索

目錄 6.3 知識檢索6.3.1 知識庫構建1&#xff09;數據采集及預處理2&#xff09;知識庫增強 6.3.2 查詢增強1&#xff09;查詢語義增強2&#xff09;查詢內容增強 6.3.3 檢索器1&#xff09;判別式檢索器2&#xff09;生成式檢索器 6.3.4 檢索效率增強1&#xff09;相似度索引算…

靜態方法和實例方法

在 Java 中&#xff0c;?靜態方法&#xff08;static method&#xff09;?和?實例方法&#xff08;instance method&#xff09;?是兩種不同類型的方法&#xff0c;它們在調用方式、內存分配和訪問權限上有顯著區別。以下是詳細對比&#xff1a; ?1. 靜態方法&#xff08;…

Lua環境搭建+Lua基本語法

前期準備&#xff1a; 搜索并下載安裝LuaForWindows,例&#xff1a; 安裝完成后開啟cmd窗口&#xff0c;輸入lua 出現版本號證明成功下載安裝 使用Sublime Text編輯器編寫Lua 使用瀏覽器或CSDN搜索Sublime Text下載并安裝&#xff0c;安裝成功后打開編輯器&#xff0c;編輯…

FFmpeg錄制屏幕和音頻

一、FFmpeg命令行實現錄制屏幕和音頻 1、Windows 示例 #include <cstdlib> #include <string> #include <iostream>int main() {// FFmpeg 命令行&#xff08;錄制屏幕 麥克風音頻&#xff09;std::string command "ffmpeg -f gdigrab -framerate 3…

【數據集】多視圖文本數據集

多視圖文本數據集指的是包含多個不同類型或來源的信息的文本數據集。不同視圖可以來源于不同的數據模式&#xff08;如原始文本、元數據、網絡結構等&#xff09;&#xff0c;或者不同的文本表示方法&#xff08;如 TF-IDF、詞嵌入、主題分布等&#xff09;。這些數據集常用于多…

C++ 繼承方式使用場景(極簡版)

1. 公有繼承&#xff08;public&#xff09; 什么時候用&#xff1f; “是一個”&#xff08;is-a&#xff09;關系&#xff1a;派生類 是 基類的一種。 例&#xff1a;class Dog : public Animal&#xff08;狗是動物&#xff09; 最常見&#xff0c;90%的繼承都用它。 2. 保…

Ubuntu 系統 Docker 中搭建 CUDA cuDNN 開發環境

CUDA 是 NVIDIA 推出的并行計算平臺和編程模型&#xff0c;利用 GPU 多核心架構加速計算任務&#xff0c;廣泛應用于深度學習、科學計算等領域。cuDNN 是基于 CUDA 的深度神經網絡加速庫&#xff0c;為深度學習框架提供高效卷積、池化等操作的優化實現&#xff0c;提升模型訓練…

高密度任務下的挑戰與破局:數字樣機助力火箭發射提效提質

2025年4月1日12時&#xff0c;在酒泉衛星發射中心&#xff0c;長征二號丁運載火箭順利升空&#xff0c;成功將一顆衛星互聯網技術試驗衛星送入預定軌道&#xff0c;發射任務圓滿完成。這是長征二號丁火箭的第97次發射&#xff0c;也是長征系列火箭的第567次發射。 執行本次任務…

關于SQL子查詢的使用策略

在 SQL 優化中&#xff0c;一般遵循**“非必要不使用子查詢”**的原則&#xff0c;因為子查詢可能會帶來額外的計算開銷&#xff0c;影響查詢效率。但是&#xff0c;并不是所有子查詢都需要避免&#xff0c;有時子查詢是最優解&#xff0c;具體要根據實際場景選擇合適的優化方式…

JavaEE初階復習(JVM篇)

JVM Java虛擬機 jdk java開發工具包 jre java運行時環境 jvm java虛擬機(解釋執行 java 字節碼) java作為一個半解釋,半編譯的語言,可以做到跨平臺. java 通過javac把.java文件>.class文件(字節碼文件) 字節碼文件, 包含的就是java字節碼, jvm把字節碼進行翻譯轉化為…

2.pycharm保姆級安裝教程

一、pycharm安裝 1.官網上下載好好軟&#xff0c;雙擊打開 2.下一步 3.修改路徑地址 (默認也可以) 4.打勾 5.安裝 不用重啟電腦 二、添加解釋器 1.雙擊軟件&#xff0c;打開 2.projects – new project 3.指定項目名字&#xff0c;項目保存地址&#xff0c;解釋器 4.右擊 – …

zk基礎—4.zk實現分布式功能二

大綱 1.zk實現數據發布訂閱 2.zk實現負載均衡 3.zk實現分布式命名服務 4.zk實現分布式協調(Master-Worker協同) 5.zk實現分布式通信 6.zk實現Master選舉 7.zk實現分布式鎖 8.zk實現分布式隊列和分布式屏障 4.zk實現分布式協調(Master-Worker協同) (1)Master-Worker架構…

Java 實現 字母異位詞分組

在這篇博客中&#xff0c;我們將詳細解析如何使用 Java 代碼來解決 字母異位詞分組這個經典的算法問題。我們會逐步分析代碼邏輯&#xff0c;并探討其時間復雜度及優化思路。 題目描述 給定一個字符串數組 strs&#xff0c;請將字母異位詞組合在一起。字母異位詞是指由相同字…

【Ragflow】10. 助理配置參數詳細解析/模型響應加速方法

概述 Ragflow的助理配置中&#xff0c;有很多參數&#xff0c;盡管官方文檔給出了一定程度的解釋&#xff0c;但不夠詳細。 本文將對各項參數進行更詳細的解釋說明&#xff0c;并進一步挖掘某些參數中隱含的潛在陷阱。 助理設置 空回復 含義&#xff1a;輸入的問題若未能在…