C++異步編程工具 async promise-future packaged_task等

深入探討 C++11 中引入的四個核心異步編程工具:std::async, std::future, std::promise, 和 std::packaged_task。它們共同構成了 C++ 現代并發編程的基礎。

為了更好地理解,我們可以使用一個餐廳點餐的類比:

  • std::future (取餐憑證):這是你點餐后拿到的“小票”或“取餐號”。你拿著它,可以稍后去查詢(wait)你的餐是否做好了,或者直接等待并取餐(get)。這個憑證本身不能做餐,只能用來獲取最終的結果。
  • std::async (套餐服務):這是最省心的方式,就像點一個“全自動套餐”。你告訴柜臺你要什么(調用函數),系統(C++運行時)會自動安排一位廚師(一個新線程)去做,并直接給你一個取餐憑證(future)。你什么都不用管,等著取就行了。
  • std::promise (后廚的承諾):這更像是后廚內部的溝通機制。假設一個廚師(生產者線程)向另一個服務員(消費者線程)承諾“我一定會把這道菜做出來”。廚師持有“承諾書”(promise),可以在菜做好后把結果放進去。服務員則持有與這份承諾書配對的“取餐憑gingzheng”(future)。這種方式將“承諾做”和“等待取”這兩個動作在不同線程中分離開來。
  • std::packaged_task (打包好的任務單):這就像一張標準化的“任務卡片”,上面寫明了要做什么菜(要執行的函數)以及附帶了一張可撕下的取餐憑證(future)。你可以把這張任務卡片創建好,但先不交給任何廚師。之后,你可以把它交給任何一個有空的廚師(任何一個線程)去執行。這非常適合任務隊列和線程池的場景。

在深入探討之前,我們先執行一次搜索,以確保所有信息的準確性和時效性。


核心概念與關系

這四個工具都定義在 <future> 頭文件中,它們的核心目標是實現線程間的同步和數據傳遞。

  • std::future 是統一的“結果接收端”。 [1][2]
  • std::async, std::promise, std::packaged_task 都可以看作是“結果的生產者”,它們都能創建一個與之關聯的 std::future 對象,但創建和使用方式各不相同。 [3][4]

下面我們逐一詳細解析。

1. std::future:未來的憑證

std::future 是一個對象,它代表了一個異步操作的最終結果。你可以把它想象成一個占位符,這個“坑”未來會被某個值或者一個異常填滿。

主要操作:

  • get(): 等待異步操作完成,然后獲取其結果。這個函數會阻塞當前線程直到結果可用。注意:get() 只能被調用一次
  • wait(): 阻塞當前線程,直到結果可用,但不獲取結果。
  • wait_for(), wait_until(): 帶超時的等待。
  • valid(): 檢查 future 是否與某個共享狀態相關聯(即是否有效)。

std::future 本身不啟動任何線程或任務,它純粹是用來接收結果的。

2. std::async:最高級的異步任務啟動器

std::async 是一個函數模板,它的作用是啟動一個異步任務,并返回一個持有該任務結果的 std::future。 [5] 這是最簡單、最直接的異步編程方式。

特點:

  • 高度封裝: 你只需要提供一個可調用對象(如函數、lambda)及其參數,std::async 會負責線程的創建和管理。
  • 啟動策略 (Launch Policy): 這是 std::async 的一個關鍵特性,可以通過第一個參數指定: [6][7]
    • std::launch::async: 強制在一個新線程中立即異步執行任務。
    • std::launch::deferred: 延遲執行。任務不會立即開始,而是在你對返回的 future 調用 get()wait() 時,才在調用者的線程中同步執行。
    • 默認(不指定或使用 std::launch::async | std::launch::deferred): 由C++運行時庫根據系統負載等情況自行決定是創建新線程還是延遲執行。
  • 析構函數行為: 由 std::async 返回的 std::future 對象,如果在其任務完成前被銷毀,其析構函數會阻塞直到任務執行完畢。 [3] 這是一個重要的陷阱,必須確保在 future 銷毀前,其結果已經被獲取或等待。

用法示例:

#include <iostream>
#include <future>
#include <thread>
#include <chrono>int long_computation(int input) {std::cout << "Thinking..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));return input * 10;
}int main() {// 啟動一個異步任務std::future<int> result_future = std::async(std::launch::async, long_computation, 5);// 在主線程中做其他事情std::cout << "Main thread is doing other work." << std::endl;// 當需要結果時,調用get()// 這會阻塞,直到 long_computation 完成int result = result_future.get();std::cout << "The result is: " << result << std::endl;return 0;
}

3. std::promise:一個明確的承諾

std::promise 提供了一種在線程間手動設置值或異常的機制。 [8][9] 它和 std::future 是一對一的“推-拉”關系:promise 負責“推”入一個值,而 future 負責“拉”取這個值。 [1][10]

特點:

  • 解耦: promise 將“設置值”的動作和“獲取值”的動作完全分離開來。你可以在一個線程中創建 promisefuture,然后將 promise 移動到另一個線程去設置值。
  • 顯式控制: 你可以精確控制何時、何地設置值(set_value)或異常(set_exception)。
  • 一次性使用: 每個 promise 只能設置一次值或異常。 [9]

用法示例:

#include <iostream>
#include <future>
#include <thread>
#include <string>void worker_thread(std::promise<std::string> p) {try {// 模擬一些工作std::this_thread::sleep_for(std::chrono::seconds(2));// 工作完成,兌現承諾p.set_value("Data from worker thread");} catch (...) {// 如果發生異常,設置異常p.set_exception(std::current_exception());}
}int main() {// 創建一個 promisestd::promise<std::string> data_promise;// 從 promise 獲取 futurestd::future<std::string> data_future = data_promise.get_future();// 啟動工作線程,并將 promise 移交給它// promise 不能被拷貝,只能移動std::thread t(worker_thread, std::move(data_promise));// 主線程做其他事情std::cout << "Main thread waiting for data..." << std::endl;// 等待并獲取結果std::string data = data_future.get();std::cout << "Received data: " << data << std::endl;t.join();return 0;
}

4. std::packaged_task:打包好的待執行任務

std::packaged_task 是一個類模板,它包裝一個可調用對象(函數、lambda等),并允許其結果被異步地獲取。 [11][12] 它像是一個將“任務”和“獲取結果的憑證”捆綁在一起的包裹。

特點:

  • 任務與執行分離: packaged_task 將任務的定義和任務的執行分離開來。你可以先創建一個 packaged_task,在稍后的某個時間點,再把它交給一個線程去執行。
  • 線程池的基石: 這個特性使得 packaged_task 成為實現線程池等任務隊列系統的理想工具。 [13][14] 你可以創建一個 packaged_task 隊列,然后讓工作線程從中取出任務并執行。
  • 自帶 Future: 創建 packaged_task 后,可以立即通過 get_future() 方法獲取與之關聯的 future 對象。 [12]

用法示例:

#include <iostream>
#include <future>
#include <thread>
#include <functional>
#include <vector>
#include <queue>int calculate_sum(int a, int b) {return a + b;
}int main() {// 1. 打包一個任務std::packaged_task<int(int, int)> task(calculate_sum);// 2. 獲取與任務關聯的 futurestd::future<int> result_future = task.get_future();// 3. 將任務移動到線程中執行// packaged_task 也不能被拷貝,只能移動std::thread t(std::move(task), 10, 20);// 主線程等待結果int result = result_future.get();std::cout << "The sum is: " << result << std::endl;t.join();return 0;
}

總結與對比

特性std::asyncstd::promisestd::packaged_task
抽象級別
核心作用啟動一個異步任務并返回 future在線程間手動傳遞一個值或異常包裝一個可調用對象,將其與 future 綁定
線程管理自動 (由運行時庫決定)手動 (需要自己創建和管理線程)手動 (需要自己將任務對象傳遞給線程執行)
耦合度任務的調用和執行緊密耦合值的“生產者”和“消費者”完全解耦任務的“定義”和“執行”解耦
主要用例簡單的“即發即忘”式異步調用復雜的線程間通信,事件驅動模型任務隊列,線程池實現

何時使用哪個?

  • 優先選擇 std::async: 如果你的需求僅僅是“在后臺運行這個函數,我稍后需要它的結果”,那么 std::async 是最簡單、最安全、最推薦的選擇。它能避免很多手動管理線程的麻煩。

  • 當需要精細控制時,使用 std::promise: 如果你的結果不是由一個簡單的函數調用產生的,而是由一系列復雜的事件或計算決定的,或者當設置值的線程和獲取值的線程生命周期完全獨立時,std::promise 提供了所需的靈活性。

  • 構建任務系統時,使用 std::packaged_task: 如果你需要創建一個任務隊列,讓一組工作線程去處理,或者需要將任務的創建和執行分離開來(例如,在主線程中創建任務,在工作線程中執行),std::packaged_task 是最合適的構建塊。它是實現線程池的完美工具。 [13][15]


Learn more:

  1. cpp-notes/future-and-promise.md at master - GitHub
  2. 【C++并發編程】std::future、std::async、std::packaged_task與std::promise的深度探索(一)-阿里云開發者社區
  3. C++ 中async、packaged_task、promise 區別及使用原創 - CSDN博客
  4. C++ 并發操作的同步 - GuKaifeng’s Blog
  5. C++ async | how the async function is used in C++ with example? - EDUCBA
  6. async、packaged_task、promise、future的區別與使用 - L_B__
  7. std::async
  8. std::promise in C++ - GeeksforGeeks
  9. std::promise - cppreference.com
  10. Concurrency in C++ : Passing Data between Threads — Promise-Future - Medium
  11. C++ – 一文搞懂std::future、std::promise、std::packaged_task、std::async的使用和相互區別-StubbornHuang Blog
  12. Packaged Task | Advanced C++ (Multithreading & Multiprocessing) - GeeksforGeeks
  13. Making a Thread Pool in C++ from scratch - DEV Community
  14. Building a Thread Pool with C++ and STL - Coding Notes
  15. Getting Started With C++ Thread-Pool | by Bhushan Rane | Medium

補充:C++20引入了一些新的異步編程工具

a. 協程 (Coroutines)

這是最具革命性的變化,它引入了一種全新的異步編程模型。協程可以看作是可以暫停和恢復的函數。

核心理念:與 std::async 啟動一個可能阻塞的線程不同,協程可以在等待一個操作(如網絡I/O)時非阻塞地掛起自身,讓出執行權。當操作完成后,它可以從掛起的位置恢復執行。這使得單個線程能夠高效管理成千上萬的并發任務。
新關鍵字:引入了 co_await, co_yield, co_return 三個關鍵字來定義和控制協程的行為。
優勢:能夠以近似同步的方式編寫邏輯清晰的異步代碼,徹底告別“回調地獄”(Callback Hell)。 它非常適合I/O密集型應用,如高性能網絡服務器。
狀態:C++20 提供了協程的底層語言支持,但上層的高級封裝仍在發展中,通常需要配合像 Boost.Asio 這樣的庫來發揮最大威力。

b. std::jthread

std::jthread 是對 std::thread 的一個安全、現代的替代品。 [8]

自動 join: jthread 的析構函數會自動調用 join(),這意味著你不再需要手動管理線程的生命周期,從而避免了忘記 join() 或 detach() 導致的程序終止問題。這是它相比 std::thread 最顯著的優勢。
協作式中斷: jthread 內置了停止令牌 (stop token) 機制 (std::stop_source, std::stop_token)。你可以從外部請求一個 jthread 停止,而線程內部可以通過檢查 stop_token 的狀態來優雅地退出循環,實現了協作式的任務取消。

c. 同步原語:std::latch 和 std::barrier

這兩個工具用于協調多個線程的執行時機。

std::latch (門閂): 這是一個一次性的同步點。 你可以初始化一個計數器,多個線程到達后使計數器減一,當計數器減到零時,所有在 wait() 處等待的線程被同時喚醒。它非常適合“等待所有工作線程準備就緒后,一起開始執行任務”的場景。
std::barrier (屏障): 與 latch 類似,但它是可重用的。當所有線程都到達屏障點后,它們被釋放,然后屏障會自動重置,可用于下一輪同步。這非常適合迭代式算法,其中每一步計算都需要所有線程同步。

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

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

相關文章

Linux-網絡管理

網絡管理1. 網絡基礎1.1 TCP/IP 協議棧&#xff08;四層模型&#xff09;1.2 網絡設備配置與基礎概念1.3 網絡接口命名規則1.4 網絡配置文件位置2. 常用網絡配置命令2.1 查看網絡接口信息2.2 配置 IP 地址2.3 啟用/禁用網卡2.4 修改網卡 MAC 地址2.5 配置網卡的 MTU&#xff08…

Linux鎖的概念及線程同步

目錄 1.常見鎖概念 死鎖 死鎖四個必要條件 避免死鎖 避免死鎖算法 2. Linux線程同步 條件變量 同步概念與競態條件 條件變量函數 初始化 銷毀 等待條件滿足 喚醒等待 簡單案例&#xff1a; 條件變量使用規范 1.常見鎖概念 死鎖 死鎖是指在一組進程中的各個進程均占有不會釋放的…

docker更換國內加速器-更換華為加速器2025-717親測可用docker 拉取鏡像出錯

[rootlocalhost ~]# docker pull nginx Using default tag: latest Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)報錯原因就是…

Unity VR多人手術模擬恢復2:客戶端移動同步問題分析與解決方案

Unity VR多人手術模擬恢復2&#xff1a;客戶端移動同步問題分析與解決方案 &#x1f3af; 問題背景 在開發基于Unity Mirror網絡架構的VR多人手術模擬系統時&#xff0c;我們遇到了一個復雜的客戶端移動同步問題&#xff1a; 主要操作者&#xff08;第一個客戶端&#xff09;&a…

uni-app開發的頁面跳轉全局加載中

uni-app開發的頁面跳轉全局加載中首先需要下載插件創建加載中頁面組件app.vue頁面中監聽跳轉首先需要下載插件 https://ext.dcloud.net.cn/plugin?id20613 創建加載中頁面組件 <!-- 全局自定義加載中 --> <template><view v-if"visible" class&qu…

XXE漏洞4-XXE無回顯文件讀取-PentesterLab靶場搭建

一.PentesterLab靶場搭建(實驗環境搭建)介紹&#xff1a;PentesterLab 是一個全面的漏洞演示平臺&#xff0c;但是它是收費的&#xff0c;我們這里只使用它的 xxe 演示案例。安裝 PentesterLab 虛擬機:下載好鏡像&#xff1a; 1.打開VMware新建虛擬機&#xff0c;選擇典型就行。…

【機器學習】圖片分類中增強常用方式詳解以及效果展示

圖片增強常用方式詳解 引言 圖片數據的質量和多樣性對模型的訓練效果起著至關重要的作用。然而&#xff0c;實際獲取的圖片數據往往存在數量不足、分布不均衡等問題。圖片增強技術應運而生&#xff0c;它通過對原始圖片進行一系列變換&#xff0c;生成更多具有多樣性的圖片&…

【URL 轉換為PDF】HTML轉換為PDF

1、方法1 pdfkit 安裝依賴 # 安裝 wkhtmltopdf&#xff08;系統級&#xff09; # Ubuntu/Debian sudo apt install wkhtmltopdf# macOS brew install wkhtmltopdf# Windows 下載安裝&#xff1a;https://wkhtmltopdf.org/downloads.html# 安裝 Python 庫 pip install pdfkitimp…

單鏈表的定義、插入和刪除

一、定義一個單鏈表 struct LNode{ //定義單鏈表節點類型ElemType data; //存放節點數據元素struct LNode *next; //指針指向下一個結點 }; //增加一個新節點&#xff1a;在內存中申請一個結點所需空間&#xff0c;并用指針p指向這個結點 struct LNode * p (struc…

Nextjs官方文檔異疑惑

第一個區別&#xff1a;不同的頁面對應的路由器設定&#xff01; 繼續用 app 路由器&#xff08;推薦&#xff0c;Next.js 未來主流&#xff09; 路由規則&#xff1a;app 目錄下&#xff0c;文件夾 page.tsx 對應路由。例如&#xff1a; app/page.tsx → 對應 / 路由&#xf…

突破AI模型訪問的“光標牢籠”:長上下文處理與智能環境隔離實戰

> 當AI模型面對浩瀚文檔卻只能處理零星片段,當關鍵信息散落各處而模型“視而不見”,我們該如何打破這堵無形的墻? 在自然語言處理領域,**輸入長度限制**(常被稱為“光標區域限制”)如同一個無形的牢籠,嚴重制約了大型語言模型(LLM)在真實場景中的應用潛力。無論是分…

AI 智能質檢系統在汽車制造企業的應用?

某知名汽車制造企業在其龐大且復雜的生產流程中&#xff0c;正面臨著棘手的汽車零部件質檢難題。傳統的人工質檢方式&#xff0c;完全依賴人工的肉眼觀察與簡單工具測量。質檢員們長時間處于高強度的工作狀態&#xff0c;精神高度集中&#xff0c;即便如此&#xff0c;由于人工…

設計模式》》門面模式 適配器模式 區別

// 復雜子系統 class CPU {start() { console.log("CPU啟動"); } } class Memory {load() { console.log("內存加載"); } } class HardDrive {read() { console.log("硬盤讀取"); } }// 門面 class ComputerFacade {constructor() {this.cpu ne…

windows內核研究(驅動開發 第一個驅動程序和調試環境搭建)

驅動開發 第一個驅動程序 驅動的開發流程 1.編寫代碼 -> 生成.sys文件 -> 部署 -> 啟動 -> 停止 ->卸載 // 編寫我們的第一個驅動程序 #include<ntddk.h>// 卸載函數 VOID DrvUnload(PDRIVER_OBJECT DriverObject) {DbgPrint("我被卸載了\n"…

ABP VNext + 多級緩存架構:本地 + Redis + CDN

ABP VNext 多級緩存架構&#xff1a;本地 Redis CDN &#x1f4da; 目錄ABP VNext 多級緩存架構&#xff1a;本地 Redis CDN一、引言 &#x1f680;二、環境與依賴 &#x1f6e0;?三、架構概覽 &#x1f310;請求全鏈路示意 &#x1f6e3;?四、本地內存緩存層 &#x1…

RGBA圖片格式轉換為RGB格式(解決convert轉換的失真問題)

使用convert轉換的問題 OpenCV 的 cv2.cvtColor(…, cv2.COLOR_BGRA2GRAY) 會直接忽略 Alpha 通道的含義&#xff0c;將它當作第四個顏色通道來處理。 轉換公式如下&#xff1a; gray 0.114*255 0.587*0 0.299*0 ≈ 29也就是說&#xff0c;即使 Alpha 為 0&#xff08;完全透…

Spring AI之Prompt開發

文章目錄1 提示詞工程1_核心策略2_減少模型“幻覺”的技巧2 提示詞攻擊防范1_提示注入&#xff08;Prompt Injection&#xff09;2_越獄攻擊&#xff08;Jailbreaking&#xff09;3 數據泄露攻擊&#xff08;Data Extraction&#xff09;4 模型欺騙&#xff08;Model Manipulat…

Java面試(基礎篇) - 第二篇!

未看第一篇的&#xff0c;這里可以直達 Java面試(基礎篇) - 第一篇 Integer對象可以用判斷嗎&#xff1f;為什么&#xff1f; 回答 不可以&#xff0c;因為 比較的是對象的實例&#xff08;內存地址&#xff09;&#xff0c;Integer是有一個緩存機制的&#xff0c;它會將-1…

【C# in .NET】11. 探秘泛型:類型參數化革命

探秘泛型:類型參數化革命 泛型是 C# 和.NET框架中一項革命性的特性,它實現了 “編寫一次,多處復用” 的抽象能力,同時保持了靜態類型的安全性和高性能。與 C++ 模板等其他語言的泛型機制不同,.NET 泛型在 CLR(公共語言運行時)層面提供原生支持,這使得它兼具靈活性、安…

菜單權限管理

菜單管理系統的整體架構1.Menu 菜單表2.role 角色表3.role_menu 角色菜 單關聯表&#xff08;多對多 &#xff09;要找role_id為3的角色能用哪個菜單:SELECT *FROM sys_menu a LEFT JOIN sys_role_menu b ON a.menu_id b.menu_id WHERE role_id3拆分開就是4.user 用戶表5.user…