協程 Coroutine

協程是 C++20 引入的新特性。

文章目錄

  • 基本概念
      • std::coroutine_handle
      • promise 類型
      • co_yield
    • 基本用法
  • 優勢
      • 異步 TCP
      • co_await

基本概念

協程(Coroutine)是一種比線程更加輕量級的并發編程模型。協程的調度由程序員手動控制。

異步不是并行,但是不阻塞~

協程的上下文切換是在用戶態完成的,不需要陷入內核,因此切換速度非常快。
當協程遇到 co_await 時,它會保存當前的上下文信息,然后讓出執行權,待異步操作完成后再恢復上下文繼續執行。切換開銷非常小,幾乎可以忽略不計。

  • 如果你在做異步編程(如網絡 I/O、文件 I/O),用 co_await

  • 如果你在實現生成器(如迭代器、流式數據處理),用 co_yield

std::coroutine_handle

協程的句柄,一個模板類——對協程進行控制與管理。

std::coroutine_handle<promise_type>;

主要方法:

  • resume():用于讓協程恢復執行。
    當協程處于掛起狀態時,調用 resume() 方法,協程就會從掛起的位置接著執行。

  • done():用于檢查協程是否已經執行完畢。
    若協程執行完畢,done() 方法會返回 true;反之,則返回 false。

  • destroy():用于銷毀協程,釋放協程占用的資源。

  • promise():返回與協程關聯的 promise 對象(struct promise_type),通過這個對象可以訪問協程的狀態和數據。

promise 類型

promise_type 對象定義了協程的行為和狀態管理方式。

promise_type 結構體中的部分方法名字是固定的,協程框架會依據這些固定的方法名來調用相應的邏輯

固定的方法名:

  • get_return_object 創建并返回協程的返回對象
  • yield_value 當協程里使用 【co_yield】 語句時,會調用此方法。它負責處理 co_yield 后面的值,并通過返回值決定協程是否要掛起。
  • initial_suspend 在協程啟動時會調用此方法,決定了協程在開始執行時是否要立即掛起。
    返回類型】有 std::suspend_always(表示協程開始就掛起,控制權交還給調用者)和 std::suspend_never(表示協程開始就執行,co_yield 后接著執行)
  • final_suspend 協程即將結束時會調用該方法,決定了協程在結束時是否要掛起。
    返回同 initial_suspend
  • unhandled_exception 當協程內部拋出未被內部捕獲的異常時,會調用這個方法來處理異常。
  • return_voidreturn_value 處理協程返回值。

co_yield

co_yield 用于在協程中產生一個值,然后暫停協程的執行,將控制權交還給調用者。

這意味著協程可以在不同的時間點多次產生值,而不是像普通函數那樣一次性返回一個結果
當調用者再次請求協程繼續執行時,協程會從 co_yield 之后的位置恢復執行。

避免了傳統異步編程中的回調地獄問題。

Generator generate_numbers() {for (int i = 0; i < 5; ++i) {co_yield i; // 生成值并掛起}
}

每執行一次 co_yield i,協程框架就會調用 Generator 類(當前協程函數的返回值類型)中 promise_type 結構體yield_value 方法,并且把 i 的值傳遞給該方法。

基本用法

簡單生成器示例,生成從 0 到 4 的整數

#include <coroutine>
#include <iostream>
#include <cstdlib>// 生成器類定義
class Generator {
public:struct promise_type { // 【定義了協程的行為】 (包括如何初始化、掛起、恢復、處理異常以及產生值等)int current_value; // 當前生成的值Generator get_return_object() { return Generator(std::coroutine_handle<promise_type>::from_promise(*this)); }std::suspend_always initial_suspend() { return {}; } // 初始掛起std::suspend_always final_suspend() noexcept { return {}; } // 最終掛起void unhandled_exception() { std::exit(1); } // 異常處理std::suspend_always yield_value(int value) { // ※ 用于處理 co_yield 語句current_value = value;return {};}void return_void() {} // 處理 co_return 語句,這里的協程不返回值。};using handle_type = std::coroutine_handle<promise_type>;//協程的句柄,對協程進行控制與管理// 迭代器類struct Iterator {handle_type coro_handle;bool operator!=(const Iterator&) const { return !coro_handle.done(); }void operator++() { coro_handle.resume(); }int operator*() const { return coro_handle.promise().current_value; } // 獲取值};// 構造函數和析構函數 : 接收/銷毀 句柄explicit Generator(handle_type h) : coro_handle(h) {}~Generator() { if (coro_handle) coro_handle.destroy(); }// 支持范圍循環Iterator begin() {if (coro_handle) coro_handle.resume();return Iterator{coro_handle};}Iterator end() { return Iterator{nullptr}; }private:handle_type coro_handle;
};// 協程函數
Generator generate_numbers() {for (int i = 0; i < 5; ++i) {co_yield i; // 生成值并掛起}
}// 使用示例
int main() {for (auto num : generate_numbers()) {std::cout << "Generated: " << num << std::endl;}return 0;
}

優勢

  • 異步非阻塞:
    協程通過 co_await 掛起執行但不阻塞線程,可以在等待 I/O 時釋放 CPU 資源
    單線程即可處理大量并發 I/O 請求(如網絡連接、文件讀寫)

像網絡IO,磁盤IO等,CPU只是調度,真正執行的是硬件:網卡和磁盤。
所以程序中高IO的情況下,不想阻塞IO,就可以使用協程。

生命周期結束,協程可以正常進行。
IO異常后,協程也會拋出異常。

  • 低開銷
    協程切換成本遠低于線程切換(通常是納秒級 vs 微秒級)。

異步 TCP

#include <boost/asio.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <coroutine>
#include <iostream>namespace asio = boost::asio;
using asio::ip::tcp;// 協程異步連接和讀寫
asio::awaitable<void> async_client() { // 該協程不返回任何值try {auto executor = co_await asio::this_coro::executor; //獲取當前協程的執行器,用于創建 tcp::socket 對象for (int i = 0; i < 5; ++i) {tcp::socket socket(executor);// 異步連接(非阻塞)co_await socket.async_connect( // 【異步連接】tcp::endpoint(asio::ip::make_address("127.0.0.1"), 8080),asio::use_awaitable // 協程掛起等, 控制權返回給事件循環,事件循環可以繼續處理其他異步操作。);// 異步發送數據std::string request = "Hello from coroutine!";co_await asio::async_write(socket, asio::buffer(request), asio::use_awaitable); // 【異步發送】// 異步接收響應char response[1024];size_t len = co_await socket.async_read_some(asio::buffer(response), asio::use_awaitable); // 【異步接收】std::cout << "Received: " << std::string(response, len) << std::endl;}} catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;}
}int main() {asio::io_context io_context; // Boost.Asio 的核心對象,負責管理所有的異步操作和事件循環。for (int i = 0; i < 5; ++i) {// 啟動協程任務asio::co_spawn(io_context, async_client(), asio::detached); //           (執行器,     協程函數,      協程任務獨立運行 不等待)}// 運行事件循環io_context.run(); return 0;
}

co_await

關鍵字。讓異步代碼可以以同步的形式編寫。

C++20 之前,沒有 co_await 這個關鍵字,異步編程通常借助回調函數或者 std::future 與 std::promise 來實現,難以維護。

后面通常跟著一個可等待對象(Awaitable),這個對象需要實現特定的接口:

  • await_ready 判斷可等待對象是否已經準備好
  • await_suspend 當 await_ready 返回 false 時,await_suspend 方法會被調用。這個方法負責掛起協程,并安排在可等待對象完成時恢復協程的執行。
  • await_resume 當可等待對象完成時,await_resume 方法會被調用。該方法的返回值會作為 co_await 表達式的結果返回給協程。
#include <iostream>
#include <coroutine>
#include <future>
#include <thread>// 定義一個可等待對象
struct Awaitable {bool await_ready() const noexcept { return false; }void await_suspend(std::coroutine_handle<> handle) const noexcept {std::thread([handle]() {std::this_thread::sleep_for(std::chrono::seconds(1));handle.resume();}).detach();}void await_resume() const noexcept {}
};// 協程函數
std::coroutine_handle<> coroutine_handle; // 協程句柄聲明,這里沒有使用
struct Task { // 協程函數的返回類型, 同“基本用法”的Generator類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 coroutine_function() {std::cout << "Coroutine started." << std::endl;co_await Awaitable{};std::cout << "Coroutine resumed after waiting." << std::endl;
}int main() {coroutine_function();std::cout << "Main function continues." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));return 0;
}

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

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

相關文章

uniapp中的流式輸出

一、完整代碼展示 目前大多數的ai對話都是流式輸出&#xff0c;也就是對話是一個字或者多個字逐一進行顯示的下面是一個完整的流式顯示程序&#xff0c;包含的用戶的消息發出和ai的消息回復 <template><view class"chat-container"><view class&quo…

洛谷題單1-P5703 【深基2.例5】蘋果采購-python-流程圖重構

題目描述 現在需要采購一些蘋果&#xff0c;每名同學都可以分到固定數量的蘋果&#xff0c;并且已經知道了同學的數量&#xff0c;請問需要采購多少個蘋果&#xff1f; 輸入格式 輸入兩個不超過 1 0 9 10^9 109 正整數&#xff0c;分別表示每人分到的數量和同學的人數。 輸…

JS 手撕題高頻考點

前端面試中&#xff0c;JS 手撕題是高頻考點&#xff0c;主要考察 編程能力、算法思維、JS 核心知識。以下是最常見的手撕題分類 代碼示例&#xff1a; 目錄 &#x1f4cc; 1. 手寫函數柯里化&#x1f4cc; 2. 手寫 debounce&#xff08;防抖&#xff09;&#x1f4cc; 3. 手寫…

【STM32】知識點介紹一:硬件知識

文章目錄 一、電源引腳簡介二、電平信號三、電路分析 一、電源引腳簡介 VCC、GND、VDD和VSS是電子電路中常見的術語&#xff0c;代表著不同的電源引腳或電壓。 VCC&#xff08;Voltage at the Common Collector&#xff09;&#xff1a;VCC是指集電極&#xff08;Collector&am…

3. 列表元素替換

【問題描述】給定一個列表&#xff0c;將列表中所有的偶數替換為0 【輸入形式】輸入一行&#xff0c;包含若干個整數&#xff0c;用空格分隔 【輸出形式】輸出替換后的列表&#xff0c;每個元素用空格分隔 【樣例輸入】1 2 3 4 5 6 7 8 9 10 【樣例輸出】1 0 3 0 5 0 7 0 9…

問題的根源還是解題的方案

周末的早上照例是要早醒 debug 代碼的&#xff0c;仿佛又回到了 2014 年… 古人幾天甚至幾個月不洗澡&#xff0c;不會臭嗎&#xff1f;有沒有可能古人沒有化纖類衣服&#xff0c;且古人的純天然生活環境其身體菌群和現代人不同&#xff0c;古人就像健康的野生動物一樣即使不洗…

虛擬機安裝linux系統無法上網的解決方法

在虛擬環境中運行Linux系統時&#xff0c;有時會遇到網絡連接問題&#xff0c;特別是在使用虛擬機軟件如VMware或VirtualBox時。本文將詳細介紹一種針對“虛擬機安裝Linux系統無法上網”問題的解決方案&#xff0c;以CentOS 6.5為例&#xff0c;適用于其他基于NAT模式的虛擬機環…

子網劃分淺度解析

文章目錄 ip地址的組成不同類型ip地址的范圍子網掩碼默認子網掩碼子網掩碼如何作用的&#xff1f;默認子網掩碼怎么作用&#xff1f; ip地址的組成 ip地址一般寫作4位點分十進制&#xff08;x.x.x.x&#xff09;&#xff0c;他們由32位二進制組成&#xff0c;每個x由8位二進制…

什么是 SEO(搜索引擎優化)?

您有網站嗎&#xff0c;或者您正在考慮創建一個網站&#xff1f;您想吸引更多人加入您的業務嗎&#xff1f;如果答案是肯定的&#xff0c;那么毫無疑問&#xff1a;SEO 應該是您營銷工作的一部分。這是建立品牌和吸引用戶訪問您的網站的好方法。但它實際上意味著什么呢&#xf…

鴻蒙HarmonyOS NEXT設備升級應用數據遷移流程

數據遷移是什么 什么是數據遷移&#xff0c;對用戶來講就是本地數據的遷移&#xff0c;終端設備從HarmonyOS 3.1 Release API 9及之前版本&#xff08;單框架&#xff09;遷移到HarmonyOS NEXT&#xff08;雙框架&#xff09;后保證本地數據不丟失。例如&#xff0c;我在某APP…

【現代深度學習技術】現代卷積神經網絡04:含并行連接的網絡(GoogLeNet)

【作者主頁】Francek Chen 【專欄介紹】 ? ? ?PyTorch深度學習 ? ? ? 深度學習 (DL, Deep Learning) 特指基于深層神經網絡模型和方法的機器學習。它是在統計機器學習、人工神經網絡等算法模型基礎上&#xff0c;結合當代大數據和大算力的發展而發展出來的。深度學習最重…

【ESP32】ESP32與MQTT通信:實現傳感器數據監測與設備控制

ESP32與MQTT通信 1 項目概覽2 硬件組成3 MQTT協議解析MQTT協議簡介MQTT核心概念本項目中的MQTT應用 4 MQTT Broker選擇EMQX Broker其他常用MQTT Broker 5 代碼解析初始化與配置MQTT消息處理發布傳感器數據 6 MQTT話題TOPIC設計7 EMQX的優勢在IoT項目中的體現8 MQTT通信流程9 應…

[特殊字符]《Curve DAO 系統學習目錄》

本教程旨在系統學習 Curve DAO 項目的整體架構、核心機制、合約設計、治理邏輯與代幣經濟等內容&#xff0c;幫助開發者全面理解其設計理念及運作方式。 目錄總覽&#xff1a; 1. Curve 項目概覽 ? 1.1 Curve 是什么&#xff1f;主要解決什么問題&#xff1f; ? 1.2 與其他…

每天一篇目標檢測文獻(六)——Part One

今天看的是《Object Detection with Deep Learning: A Review》 目錄 一、摘要 1.1 原文 1.2 翻譯 二、介紹 2.1 信息區域選擇 2.2 特征提取 2.3 分類 三、深度學習的簡要回顧 3.1 歷史、誕生、衰落和繁榮 3.2 CNN架構和優勢 一、摘要 1.1 原文 Due to object dete…

Arthas線上問題診斷器

Arthas是Alibaba開源的java診斷工具 解決問題 這個類從哪個jar 包加載的&#xff1f;為什么會報各種相關的Exception&#xff1f; 遇到問題無法在線上debug&#xff0c;不能直通過加載日志再重新發布 有什么辦法可以監控到JVM的實時運行狀態&#xff1f; …

[Lc5_dfs+floodfill] 簡介 | 圖像渲染 | 島嶼數量

目錄 0.floodfill算法簡介 1.圖像渲染 題解 2.島嶼數量 題解 之前我們在 bfs 中有介紹過[Lc15_bfsfloodfill] 圖像渲染 | 島嶼數量 | 島嶼的最大面積 | 被圍繞的區域&#xff0c;現在我們來看看 dfs 又是如何解決的呢 0.floodfill算法簡介 floodfill算法又叫洪水灌溉或者…

JVM類加載器詳解

文章目錄 1.類與類加載器2.類加載器加載規則3.JVM 中內置的三個重要類加載器為什么 獲取到 ClassLoader 為null就是 BootstrapClassLoader 加載的呢&#xff1f; 4.自定義類加載器什么時候需要自定義類加載器代碼示例 5.雙親委派模式類與類加載器雙親委派模型雙親委派模型的執行…

Chapters 15 16:What Is Architecture?Independence_《clean architecture》notes

What Is Architecture?&Independence **Chapter 15: What Is Architecture?****Key Concepts**:**Code Example: Layered Architecture**: **Chapter 16: Independence****Key Concepts**:**Code Example: Dependency Inversion & Interfaces**: **Combined Example:…

【SPP】RFCOMM 層在SPP中互操作性要求深度解析

藍牙串口協議&#xff08;SPP&#xff09;通過 RFCOMM 協議實現 RS232 串口仿真&#xff0c;其互操作性是設備互聯的關鍵。本文基于藍牙核心規范&#xff0c;深度解析 RFCOMM 層的能力矩陣、信號處理、流控機制及實戰開發&#xff0c;結合狀態機、流程圖和代碼示例&#xff0c;…

阻塞式IO與非阻塞IO的區別

阻塞式IO與非阻塞IO的區別 1. 阻塞式IO (Blocking I/O) 定義 當程序發起一個I/O操作&#xff08;如讀取文件、網絡數據&#xff09;時&#xff0c;進程會被掛起&#xff08;阻塞&#xff09;&#xff0c;直到操作完成或超時才會繼續執行后續代碼。在此期間&#xff0c;程序無法…