C++ 语言特性29 - 协程介绍

news2024/10/6 12:22:53

一:什么是协程

        C++20 引入了协程(coroutine),这是 C++ 标准库中一个强大的新特性。协程是一种可以在执行中暂停并随后恢复的函数,允许程序在异步或并行场景下高效管理任务,而不需要传统的线程或复杂的回调机制。

        协程是可以暂停其执行并保存其当前状态,稍后可以从该位置恢复执行的特殊函数。在某种程度上,协程类似于普通函数,但它们的执行流可以通过 co_awaitco_yieldco_return 来暂停和恢复。这与传统函数的行为不同,传统函数一旦开始执行,就会一直运行到返回或退出为止。

        协程与线程的不同在于,协程不会引入新的线程,它们是在现有线程上执行的。协程的切换开销通常非常低,远小于线程切换,因此在处理高并发场景下具有很大的优势。

二:协程的语法

     C++20 协程的语法引入了三种关键字:

  1. co_return:从协程中返回值。
  2. co_yield:暂停协程,并返回一个值给调用者,稍后可以恢复协程。
  3. co_await:暂停协程,等待某个异步操作完成后再继续执行。

        为了使用协程,C++ 中的一个普通函数必须返回某种特殊的类型,而不是像 void 或普通类型那样。这个特殊的返回类型需要符合协程的约定,它定义了如何控制协程的生命周期。下面举一个例子,演示下协程的使用:

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>

// 一个简单的协程返回对象类型
struct Task {
    struct promise_type {
        Task get_return_object() {
            return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_never initial_suspend() { return {}; }  // 协程立即开始执行
        std::suspend_always final_suspend() noexcept { return {}; } // 协程结束时暂停
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;

    Task(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Task() { handle.destroy(); }

    void resume() { handle.resume(); }
};

// 一个自定义的 awaitable 类型,用于模拟异步任务
struct Awaiter {
    bool await_ready() { return false; } // 表示协程需要挂起
    void await_suspend(std::coroutine_handle<>) {
        // 模拟异步任务的延迟,例如 I/O 操作
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    void await_resume() {}
};

// 协程函数,模拟异步任务
Task async_task() {
    std::cout << "Task started, waiting for 1 second...\n";
    co_await Awaiter{};  // 挂起协程并等待模拟异步任务
    std::cout << "Task resumed after 1 second!\n";
}

int main() {
    Task task = async_task();
    task.resume();  // 启动协程
    std::cout << "Main function continues execution...\n";
}

上面代码中主要变量和函数如下; 

  • 协程句柄 (std::coroutine_handle)

    std::coroutine_handle 是一个可以用来控制协程生命周期的类型。它可以暂停、恢复协程以及销毁协程。std::coroutine_handle 提供了一些常用的成员函数,例如 resume()destroy() 等,用于管理协程的执行。

  • Promise 对象

    每个协程都有一个与之关联的 promise 对象(promise_type)。它负责管理协程的状态和返回值。promise_type 需要实现一些特定的函数,比如 get_return_object()(返回协程的返回对象)、initial_suspend()(定义协程开始时的行为)和 final_suspend()(定义协程结束时的行为)。

  • 挂起点 (co_await)

    co_await 是协程的核心,它允许协程暂停执行,等待某个条件满足时恢复。co_await 的行为依赖于其后跟随的对象或表达式,它可以是等待一个异步操作完成,也可以是一个延迟或其他触发条件。

    协程的暂停和恢复类似于保存函数的状态,并在以后恢复它的执行,这样可以避免使用回调或复杂的状态机来处理异步操作

  • 协程函数 async_task

    • 使用 co_await 关键字等待 Awaiter 类型,这会暂停协程,并在一秒钟后恢复。
    • 恢复后,协程继续执行并打印消息。
  • 自定义的 Awaiter

    • await_ready 返回 false,表明协程需要挂起。
    • await_suspend 中使用 std::this_thread::sleep_for 来模拟一个异步任务(实际情况中可以是 I/O 操作或其他异步任务)。
    • await_resume 恢复协程。
  • main 函数

    • 调用 async_task 来创建协程对象 task,并使用 resume 来启动协程。

 这个例子运行过程如下:

  • 协程开始执行,打印 Task started, waiting for 1 second...,然后协程挂起 1 秒钟。
  • 主函数继续执行并打印 Main function continues execution...
  • 1 秒钟后,协程恢复执行并打印 Task resumed after 1 second!

三:协程的使用场景

1. 异步 I/O 操作

      在传统的同步代码中,I/O 操作(如网络或磁盘操作)通常会阻塞线程,导致线程空闲等待。在 C++ 中使用协程,可以使得这些 I/O 操作异步化,协程可以在 I/O 操作完成时继续执行,而不阻塞整个线程。在这个例子中,协程 sleep_one_second 会暂停执行 1 秒,然后恢复。

#include <iostream>
#include <chrono>
#include <thread>
#include <coroutine>

struct Sleeper {
    struct promise_type {
        Sleeper get_return_object() { return {}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    bool await_ready() { return false; }
    void await_suspend(std::coroutine_handle<>) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    void await_resume() {}
};

Sleeper sleep_one_second() {
    std::cout << "Sleeping..." << std::endl;
    co_await Sleeper{};
    std::cout << "Awake!" << std::endl;
}
2. 生成器

协程可以用来实现生成器模式,允许在迭代过程中动态生成值,而不需要一次性返回整个集合。这个例子中的 range 协程会生成一个从 0 到 4 的整数序列,类似于 Python 的生成器。

#include <iostream>
#include <coroutine>
#include <vector>

struct Generator {
    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 {}; }
        std::suspend_always yield_value(int value) {
            current_value = value;
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;

    Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }

    bool move_next() {
        handle.resume();
        return !handle.done();
    }

    int current_value() { return handle.promise().current_value; }
};

Generator range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;
    }
}

int main() {
    auto gen = range(0, 5);
    while (gen.move_next()) {
        std::cout << gen.current_value() << " ";
    }
}

四:协程的优势

  1. 简化异步代码:协程可以以同步风格编写异步代码,简化了复杂的异步操作,如异步 I/O、网络通信、文件读写等。
  2. 高性能:协程切换的开销通常比线程上下文切换低,因此在高并发场景下协程更为高效。
  3. 提高代码可读性:协程使得代码更加线性和易读,避免了大量嵌套的回调或状态机逻辑。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2191873.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[ESP32]ESP-IDF使用组件添加U8g2图形库

U8g2 在ESP32使用u8g2的时候可以使用添加component的方式进行, 由于官方的component库没有, 这里我找到了一个可以使用的github库, 使用git的方式进行添加这一个库 具体的原理可以看[官方手册](https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32/api-guides/to…

《Windows PE》4.1.4 手工重构导入表

接下来我们做一个稍微复杂一些的实验&#xff0c;实验需要四个程序&#xff1a; HelloWorld.exe&#xff1a;弹出MessageBox窗口&#xff08;实验1已实现&#xff09;。 Regedit.exe&#xff1a;添加注册表启动项。 LockTray.exe&#xff1a;锁定任务栏窗口。 UnLockTray.exe&…

各省份农林牧渔业总产值统计数据(2000-2022年)

数据来源 《中国统计年鉴》 农业、林业、牧业和渔业作为国民经济的重要组成部分&#xff0c;对于保障国家粮食安全、促进区域经济发展、实现农业农村现代化和乡村振兴具有重要意义。 2000年-2022年全国各省份农林牧渔业总产值统计数据&#xff08;大数据&#xff09;.zip资源…

DBMS-3.1 SQL(1)——SQL概述和DDL

本文章的素材与知识来自李国良老师和王珊老师。 SQL概述 1.定义 2.SQL语句分类 数据定义语言DDL&#xff08;Data Definition Language&#xff09; 一.表 1.创建表 &#xff08;1&#xff09;语法 中括号内的项为可选项。分号标志着一条SQL语句的结束。SQL语句不区分大小…

【Spring Boot React】Spring Boot和React教程 完整版

【Spring Boot & React】Spring Boot和React教程 在B站找到一个不错的SpringBoot和React的学习视频&#xff0c;作者是amigoscode 【Spring Boot & React】Spring Boot和React教程 2023年更新版【Spring Boot React】价值79.9美元&#xff0c;全栈开发&#xff0c;搭…

付费计量系统实体和接口(1)

13.System entities and interfaces 系统实体和接口 See also Clause 4 for a discussion on general concepts and Clause 5 for generic entity model. 参见条目 4 讨论总体概念、条目 5 通用实体模型。 An entity specification should specify the embodied functions and …

【Unity踩坑】UWP项目安装包认证失败

问题&#xff1a;在Unity导出的VS项目&#xff0c;打包生成appx后&#xff0c;进行应用认证时失败。提示部分API不支持。 API __C_specific_handler in kernel32.dll is not supported for this application type. UnityPlayer.dll calls this API.API DXGIGetDebugInterface1 …

【Linux】进程控制(创建、终止、等待、替换)

文章目录 1. 进程创建2. 进程终止3. 进程等待4. 进程程序替换4.1 认识进程替换4.2 认识全部接口 1. 进程创建 如何创建进程我们已经在之前学习过了&#xff0c;无非就是使用fork()&#xff0c;它有两个返回值。创建成功&#xff0c;给父进程返回PID&#xff0c;给子进程返回0&…

LLM大模型常见面试题汇总(含答案)

最近秋招正在如火如荼地进行中&#xff0c;看到很多人的简历上都包含大模型相关的工作&#xff0c;各家大厂和初创都很舍得给钱&#xff0c;动辄百万年包也变得不再稀奇。 因此在大模型纵横的这个时代&#xff0c;不仅大模型技术越来越卷&#xff0c;就连大模型相关的岗位和面…

零点校准说明

目录 注意&#xff0c;校准时电机不能带负载&#xff0c;否则校准容易出错。 零点校准 点击【零点校准】&#xff0c;显示“校准中…”, 如果是“High Current”类型电机&#xff0c;电机先“嘀”一声测量电阻电感&#xff08;耗时约5秒&#xff09;&#xff0c;然后再正转一圈…

【实战】Nginx+Lua脚本+Redis 实现自动封禁访问频率过高IP

大家好&#xff0c;我是冰河~~ 自己搭建的网站刚上线&#xff0c;短信接口就被一直攻击&#xff0c;并且攻击者不停变换IP&#xff0c;导致阿里云短信平台上的短信被恶意刷取了几千条&#xff0c;加上最近工作比较忙&#xff0c;就直接在OpenResty上对短信接口做了一些限制&am…

Vivado viterbi decoder license

Viterbi Decoder 打卡以上链接 添加后next后&#xff0c; 会发送lic文件到邮件&#xff0c;vivado导入lic即可

Latex技巧——参考文献中加入url和doi

有的期刊要求在参考文献里加入url或者doi, 例如下图中蓝色的字体。 在bib里编辑为下图中note行&#xff0c;也就是利用\href命令。\href后第一个{}内为网址&#xff0c;第二个{}为在参考文献中显示的蓝色文字。一般来说&#xff0c;两个{}内的文字相同。若遇到有些网址有下划线…

不是哥们,真有人拿 AI 来面试啊?

大家好&#xff0c;我是八哥。如今 AI 已经渗透到了我们的生活、求职、工作等方方面面。就拿求职来说&#xff0c;今年的美团校招&#xff0c;已经用上了 AI 来当面试官&#xff0c;降本增效。 我自己也当过面试官&#xff0c;其实对 AI 面试官还是挺好奇的。毕竟每个候选人的…

UOM无人机空域快速申请技术详解

UOM无人机空域快速申请技术详解主要包括以下几个步骤&#xff1a; 一、准备阶段 1. 实名登记&#xff1a;首先&#xff0c;您需要在相应的民航部门进行无人机的实名登记&#xff0c;这是合法飞行的前提。 2. 了解规定&#xff1a;熟悉并遵守当地关于无人机飞行的法律法规&am…

C语言:预编译过程的剖析

目录 一.预定义符号和#define定义常量 二.#define定义宏 三.宏和函数的对比 四、#和##运算符 五、条件编译 在之前&#xff0c;我们已经介绍了.c文件在运行的过程图解&#xff0c;大的方面要经过两个方面。 一、翻译环境 1.预处理&#xff08;预编译&#xff09; 2.编译 3…

SpringBoot在线教育系统:从零到一的构建过程

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理微服务在线教育系统的相关信息成为必然。开…

如何禁止电脑蓝牙?分享五个有效方法,快@企业老板来看! 内行人都在用的方法哦,不妨一试!

在现代办公环境中&#xff0c;电脑蓝牙功能的便捷性无可否认&#xff0c;但随之而来的安全风险也不容忽视。 尤其对于企业而言&#xff0c;蓝牙的开放可能引发数据泄露、未经授权的设备连接等一系列安全问题。 因此&#xff0c;如何有效地禁止电脑蓝牙&#xff0c;成为了许多…

终于有人把多模态大模型讲这么详细了

多模态大型语言模型&#xff08;Multimodal Large Language Models&#xff0c; MLLM&#xff09;的出现是建立在大型语言模型&#xff08;Large Language Models&#xff0c; LLM&#xff09;和大型视觉模型&#xff08;Large Vision Models&#xff0c; LVM&#xff09;领域不…

付费计量系统通用功能(14)

11.18 Function_Object功能目标 11.18.1 Function_Object definition功能目标定义 A Function_Object is an abstract notation of defining the attributes of a function. 功能目标是一个抽象的功能属性的定义 Definition of a function object should stat…