詳解c++20的協程,自定義可等待對象,生成器詳解

協程

c++20的協程三大標簽:“性能之優秀”,“開發之靈活”,“門檻之高”

在講解c++的協程使用前,我們需要先明白協程是什么,協程可以理解為用戶態的線程,它需要由程序來進行調度,如上下文切換與調度設計都需要程序來設計,并且協程運行在單個線程中,這就成就了協程的低成本,更直接一點的解釋可以說,協程就是一種可以被掛起與恢復的特殊函數。
協程并不會真正地“脫離”當前的線程,它只是讓控制流從一個函數流轉到另一個地方,然后再回來。這個過程是 輕量級非阻塞 的。
協程可以分為兩種類型對稱型與非對稱型協程:

  • 非對稱協程:控制權的轉移是單向的,總是從調用者協程轉移到被調用者協程,并且被調用者協程必須將控制權返回給調用者協程。
  • 對稱協程:控制權可以在任意兩個協程之間直接轉移。協程在執行過程中可以選擇將控制權交給其他任何協程,而不依賴于特定的調用層次關系。

c++協程的基本概念

c++提供的協程是典型的非對稱協程,c++中的協程有兩個特定條件:

  1. 協程函數必須包含至少一個協程關鍵字(co_awaitco_yieldco_return
    • co_await: 主要用于暫停協程的執行,同時等待某個異步操作完成。在協程執行到 co_await 表達式時,它會將控制權交還給調用者,待異步操作完成后,協程再恢復執行。
    • co_yield: 主要用于生成器模式,它會產生一個值,然后暫停協程的執行。每次調用生成器的迭代器時,協程會恢復執行,直到下一個 co_yield 或者協程結束。
    • co_return: 用于結束協程的執行,并返回一個值(如果協程有返回類型)。當協程執行到 co_return 時,它會銷毀自身的棧幀,并將控制權交還給調用者。
      這里先簡單介紹協程的基本概念和使用,后面會結合更多代碼解釋每一個點
  2. 返回值必須是實現了promise_type結構體的一個類或者結構體;
struct Task {struct promise_type {Task get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() {}};
};Task task() {std::cout << "Starting coroutine operation..." << std::endl;co_await std::suspend_always{};std::cout << "Coroutine operation completed." << std::endl;
}int main() {task();std::cout << "Main function continues..." << std::endl;return 0;
}// 輸出
Starting coroutine operation...
Main function continues...

這個代碼簡單演示了協程的使用,我們可以看到輸出只有兩條,并沒有輸出Coroutine operation completed.,這個程序的運行流程是main函數調用task后,先保存當前上下文,再將當前線程的上下文切換成task的,接下來輸出一條內容運行到co_await時,再次保存當前上下文,不過這次會將上下文切換回協程的調用者也就是main,我們后續沒有再回到task運行最后一條命令,這里也就沒有相關輸出了
協程函數的返回類型有特殊要求。返回類型主要用于表示協程的句柄或包裝器,它提供了一種方式來管理協程的生命周期,以及與協程進行交互。promise_type主要用來定義協程的生命周期和行為。

  • promise_type必須實現以下關鍵方法:
    • get_return_object():該函數在協程開始執行之前被調用,其作用是返回協程的返回對象
    • initial_suspend():在協程開始執行時調用,用于決定協程是否在開始時就暫停。std::suspend_never 表示協程不會在開始時暫停,會立即執行。
    • final_suspend():在協程結束執行時調用,用于決定協程是否在結束時暫停。std::suspend_never 表示協程不會在結束時暫停,會立即銷毀。
    • unhandled_exception():處理未捕獲的異常。
      下面的方法可選
  • return_void() 當調用co_return時會觸發這個函數,它觸發于final_suspend()之前
  • yield_value() 主要用于生成器返回值
    我們再來明確一下調用時刻,get_return_object()是在協程運行前就被調用了,當存放task();這一行時就會調用get_return_object()函數,因為我們這個演示比較簡單不會返回如何東西,我們會在下面的演示中詳細介紹,initial_suspend()是在協程開始執行時調用,這個時刻是介于get_return_object()與真正開始運行之間的時候,這些函數名必須一模一樣

生成器

template<typename T>
struct Generator {struct promise_type {T value_;auto get_return_object() { return Generator{this}; }auto initial_suspend() noexcept { return std::suspend_always{}; }auto final_suspend() noexcept { return std::suspend_always{}; }void unhandled_exception() { std::terminate(); }auto yield_value(T val) {value_ = val;return std::suspend_always{};}};using Handle = std::coroutine_handle<promise_type>;Handle handle_;explicit Generator(promise_type* p) : handle_(Handle::from_promise(*p)) {}~Generator() { if (handle_) handle_.destroy(); }T next() {if (!handle_.done()) handle_.resume();return handle_.promise().value_;}
};// 斐波那契生成器
Generator<int> fibonacci() {int a = 0, b = 1;while (true) {co_yield a;int temp = a;a = b;b = temp + b;}
}int main() {auto fib = fibonacci();for (int i = 0; i < 10; ++i) {std::cout << fib.next() << " "; }
}// 輸出 
0 1 1 2 3 5 8 13 21 34

這個代碼使用了一種叫做生成器的特殊類,它需要我們自己實現,作用是獲得一次協程的返回值,fibonacci中使用co_yield返回了a,之前我們介紹過co_yield它的作用是,產生一個值然后暫停協程的執行
這里的promise_type結構體多了兩個東西,yield_value()函數的作用是把傳入的值存儲到 value_ 中,并且返回 std::suspend_always{},使得協程暫停。當使用co_yield關鍵字時就相當于調用了這個函數,T value_用于存放我們的返回值
promise_type結構體外的東西,都是我們可以自定義的這里最重要的就是,代碼中的std::coroutine_handle<>,它是一個協程句柄表示一個協程實例的句柄,允許開發者手動控制協程的恢復、銷毀或訪問其內部狀態,它通常實現為一個指針,直接指向協程的狀態幀 ,C++20 協程機制中的核心工具類,用于直接操作協程的生命周期和執行流程。它提供了一種底層但靈活的方式來管理協程的狀態。我們介紹幾個常用的函數

  • Handle::from_promise() 這是一個靜態成員函數,用于從 promise_type 對象創建一個協程句柄。在協程的 promise_type 中,通常會使用這個函數來獲取協程句柄,以便在返回對象中保存。
  • resume() 恢復協程的執行。如果協程當前處于暫停狀態,調用這個函數會讓協程從暫停點繼續執行,直到下一個暫停點或者協程結束。
  • destroy() 銷毀協程,釋放協程占用的資源。在協程執行完畢或者不再需要時,應該調用這個函數來避免內存泄漏。
  • bool done() 檢查協程是否已經完成。如果協程已經執行完畢,返回 true;否則返回 false
  • promise_type& promise() 返回與協程句柄關聯的 promise_type 對象的引用。通過這個引用,你可以訪問和修改協程的狀態和數據。
    next()函數用于返回值并恢復協程的執行,這里我們通過get_return_object()構建并返回我們的生成器

自定義可等待對象

自定義可等待對象需要滿足特定的接口要求,主要涉及await_readyawait_suspendawait_resume這三個成員函數。

  • await_ready:用于判斷是否可以立即恢復協程的執行。若返回true,協程會馬上恢復;若返回false,協程就會被掛起。
  • await_suspend:在協程掛起時調用,這里可以執行異步操作。在示例中,使用一個新線程來模擬異步操作,操作完成后恢復協程。
  • await_resume:在協程恢復執行時調用,返回異步操作的結果。
template <typename T>
struct FutureAwaitable {std::future<T>& future;bool await_ready() const noexcept {return future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;}void await_suspend(std::coroutine_handle<> handle) const {std::thread([this, handle]() mutable {future.wait();handle.resume();}).detach();}T await_resume() {return future.get();}
};template <typename T>
FutureAwaitable<T> co_awaitable(std::future<T>& future) {return {future};
}struct AsyncTask {struct promise_type {std::promise<int> promise;std::future<int> result = promise.get_future();auto get_return_object() { return AsyncTask{this}; }auto initial_suspend() { return std::suspend_never{}; }auto final_suspend() noexcept { return std::suspend_always{}; }void return_value(int val) {promise.set_value(val);}void unhandled_exception() {std::terminate();}};using Handle = std::coroutine_handle<promise_type>;Handle handle_;explicit AsyncTask(promise_type* p) : handle_(Handle::from_promise(*p)) {}~AsyncTask() {if (handle_) {handle_.destroy();}}int get() {return handle_.promise().result.get();}
};AsyncTask simulate_async_io() {auto future = std::async([] {std::this_thread::sleep_for(std::chrono::seconds(1));return 42;});auto result = co_await co_awaitable(future);co_return result;
}int main() {auto task = simulate_async_io();std::cout << "Waiting..." << std::endl;std::cout << "Result: " << task.get() << std::endl; // 輸出: 42return 0;
}

這個代碼演示了co_await結合自定義等待對象,實現的異步調用,當我們觸發co_await時會等待異步操作的完成,并獲得返回值,在等待時協程會掛起讓出cpu資源
await_suspend()函數中我們在另一個線程中恢復了協程,協程的執行線程由恢復它的線程決定。handle.resume() 是恢復協程執行的關鍵操作,它會從協程上次掛起的位置接著執行。由于 handle.resume() 是在新創建的 std::thread 線程里被調用的,所以協程恢復后會繼續在這個新線程里執行后續代碼。
return_value()yield_value()函數類似,它是用來處理co_return關鍵字的返回值的

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

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

相關文章

JavaEE企業級開發 延遲雙刪+版本號機制(樂觀鎖) 事務保證redis和mysql的數據一致性 示例

提醒 要求了解或者熟練掌握以下知識點 spring 事務mysql 臟讀如何保證緩存和數據庫數據一致性延遲雙刪分布式鎖并發編程 原子操作類 前言 在起草這篇博客之前 我做了點功課 這邊我寫的是一個示例代碼 數據層都寫成了 mock 的形式(來源于 JUnit5) // Dduo import java.u…

A2 最佳學習方法

記錄自己想法的最好理由是發現自己的想法&#xff0c;并將其組織成可傳播的形式 (The best reason for recording what one thinks is to discover what one thinks and to organize it in transmittable form.) Prof Ackoff 經驗之談&#xff1a; 做培訓或者寫文章&#xff…

嵌入式硬件工程師從小白到入門-PCB繪制(二)

PCB繪制從小白到入門&#xff1a;知識點速通與面試指南 一、PCB設計核心流程 需求分析 明確電路功能&#xff08;如電源、信號處理、通信&#xff09;。確定關鍵參數&#xff08;電壓、電流、頻率、接口類型&#xff09;。 原理圖設計 元器件選型&#xff1a;匹配封裝、電壓、…

vue創建子組件步驟及注意事項

在 Vue 中創建子組件需要遵循組件化開發的核心原則&#xff0c;并注意數據流、通信機制、復用性等關鍵點。以下是詳細步驟和注意事項&#xff0c;結合代碼示例說明&#xff1a; 一、創建子組件的步驟 1. 定義子組件 創建一個 .vue 文件&#xff08;單文件組件&#xff09;&am…

Cocos Creator版本發布時間線

官網找不到&#xff0c;DeepSeek給的答案&#xff0c;這里做個記錄。 Cocos Creator 1.x 系列 發布時間&#xff1a;2016 年 - 2018 年 1.0&#xff08;2016 年 3 月&#xff09;&#xff1a; 首個正式版本&#xff0c;基于 Cocos2d-x 的 2D 游戲開發工具鏈&#xff0c;集成可…

【Spring AI】基于專屬知識庫的RAG智能問答小程序開發——功能優化:用戶鑒權主體功能開發

系列文章目錄 【Spring AI】基于專屬知識庫的RAG智能問答小程序開發——完整項目&#xff08;含完整前端后端代碼&#xff09;【Spring AI】基于專屬知識庫的RAG智能問答小程序開發——代碼逐行精講&#xff1a;核心ChatClient對象相關構造函數【Spring AI】基于專屬知識庫的R…

【AI神經網絡】深度神經網絡(DNN)技術解析:從原理到實踐

引言 深度神經網絡&#xff08;Deep Neural Network, DNN&#xff09;作為人工智能領域的核心技術&#xff0c;近年來在計算機視覺、自然語言處理、醫療診斷等領域取得了突破性進展。與傳統機器學習模型相比&#xff0c;DNN通過多層非線性變換自動提取數據特征&#xff0c;解決…

目標跟蹤——deepsort算法詳細闡述

deepsort 算法詳解 Unmatched Tracks(未匹配的軌跡) 本質角色: 是已存在的軌跡在當前幀中“失聯”的狀態,即預測位置與檢測結果不匹配。 生命周期階段: 已初始化: 軌跡已存在多幀,可能攜帶歷史信息(如外觀特征、運動模型)。 未被觀測到: 當前幀中未找到對應的檢測框…

Vue-admin-template安裝教程

#今天配置后臺管理模板發現官方文檔的鏡像網站好像早失效了&#xff0c;自己稍稍總結了一下方法# 該項目環境需要node17及以下&#xff0c;如果npm install這一步報錯可能是這個原因 git clone https://github.com/PanJiaChen/vue-admin-template.git cd vue-admin-template n…

Rust從入門到精通之進階篇:14.并發編程

并發編程 并發編程允許程序同時執行多個獨立的任務&#xff0c;充分利用現代多核處理器的性能。Rust 提供了強大的并發原語&#xff0c;同時通過類型系統和所有權規則在編譯時防止數據競爭和其他常見的并發錯誤。在本章中&#xff0c;我們將探索 Rust 的并發編程模型。 線程基…

算法訓練營第二十三天 | 貪心算法(一)

文章目錄 一、貪心算法理論基礎二、Leetcode 455.分發餅干二、Leetcode 376. 擺動序列三、Leetcode 53. 最大子序和 一、貪心算法理論基礎 貪心算法是一種在每一步選擇中都采取當前狀態下的最優決策&#xff0c;從而希望最終達到全局最優解的算法設計技術。 基本思想 貪心算…

css基礎-display 常用布局

CSS display 屬性詳解 屬性設置元素是否被視為塊級或行級盒子以及用于子元素的布局&#xff0c;例如流式布局、網格布局或彈性布局。 一、基礎顯示模式 1. block 作用&#xff1a; 元素獨占一行可設置寬高和內外邊距默認寬度撐滿父容器 應用場景&#xff1a; 布局容器&a…

速賣通API數據清洗實戰:從原始JSON到結構化商品數據庫

下面將詳細介紹如何把速賣通 API 返回的原始 JSON 數據清洗并轉換為結構化商品數據庫。 1. 數據獲取 首先要借助速賣通 API 獲取商品數據&#xff0c;以 Python 為例&#xff0c;可使用requests庫發送請求并得到 JSON 數據。 import requests# 替換為你的 API Key 和 Secret …

【零基礎入門unity游戲開發——2D篇】2D物理系統 —— 2D剛體組件(Rigidbody2D)

考慮到每個人基礎可能不一樣,且并不是所有人都有同時做2D、3D開發的需求,所以我把 【零基礎入門unity游戲開發】 分為成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要講解C#的基礎語法,包括變量、數據類型、運算符、流程控制、面向對象等,適合沒有編程基礎的…

Collectors.toMap / list 轉 map

前言 略 Collectors.toMap List<User> userList ...; Map<Long, User> userMap userList.stream().collect(Collectors.toMap(User::getUserId, Function.identity()));假如id存在重復值&#xff0c;則會報錯Duplicate key xxx, 解決方案 兩個重復id中&#…

熱門面試題第13天|Leetcode 110.平衡二叉樹 257. 二叉樹的所有路徑 404.左葉子之和 222.完全二叉樹的節點個數

222.完全二叉樹的節點個數&#xff08;優先掌握遞歸&#xff09; 需要了解&#xff0c;普通二叉樹 怎么求&#xff0c;完全二叉樹又怎么求 題目鏈接/文章講解/視頻講解&#xff1a;https://programmercarl.com/0222.%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8…

關于Object.assign

Object.assign 基本用法 Object.assign() 方法用于將所有可枚舉屬性的值從一個或者多個源對象source復制到目標對象。它將返回目標對象target const target { a: 1, b: 2 } const source { b: 4, c: 5 }const returnedTarget Object.assign(target, source)target // { a…

GitHub高級篩選小白使用手冊

GitHub高級篩選小白使用手冊 GitHub 提供了強大的搜索功能&#xff0c;允許用戶通過高級篩選器來精確查找倉庫、Issues、Pull Requests、代碼等。下面是一些常用的高級篩選用法&#xff0c;幫助你更高效地使用 GitHub 搜索功能。 目錄 搜索倉庫搜索Issues搜索Pull Requests搜…

手動集成sqlite的方法

注意到sqlite有backup方法&#xff08;https://www.sqlite.org/backup.html&#xff09;。 也注意到android中sysroot下&#xff0c;沒有sqlite3的庫&#xff0c;也沒有相關頭文件。 如果要使用 sqlite 的backup&#xff0c;那么就需要手動集成sqlite代碼到項目中。可以如下操…

藍橋杯真題 2109.統計子矩陣

原題地址:1.統計子矩陣 - 藍橋云課 問題描述 給定一個 NMNM 的矩陣 AA, 請你統計有多少個子矩陣 (最小 1111, 最大 NM)NM) 滿足子矩陣中所有數的和不超過給定的整數 KK ? 輸入格式 第一行包含三個整數 N,MN,M 和 KK. 之后 NN 行每行包含 MM 個整數, 代表矩陣 AA. 輸出格…