C++20新特性解析:深入探讨协程库的实现原理与应用

news2024/9/23 23:33:10

C++20新特性解析:深入探讨协程库的实现原理与应用

  • 一、C++20的协程库简介
  • 二、C++20协程基础知识
    • 2.1、协程的基本概念和使用方法
    • 2.2、C++20中的协程支持
    • 2.3、协程与传统线程的对比
  • 三、C++20协程库的实现原理
  • 四、C++20协程库的应用实例
  • 总结

一、C++20的协程库简介

C++20引入了对协程的支持,这是一项重要的编程语言特性,可以简化异步编程的实现而且提高代码的可读性和可维护性。协程可以在执行过程中暂停和恢复,能够更直观地表达异步操作的流程,让编程更加简洁和高效。

C++20的协程库提供了一组新的关键字、库函数和概念,能轻松地实现异步操作、事件驱动的编程模型和无阻塞式IO等。这些特性对于网络编程、并发编程和响应式编程都有很大的帮助。在C++20之前,一般都是使用第三方库或者自己实现协程功能,现在C++20的协程库为协程的使用提供了官方标准的支持,为C++编程带来了全新的可能性。

协程在异步编程中的重要性:

  1. 传统的异步编程方式(如回调、Promise等)会使代码结构复杂、难以理解和维护。而协程可以让异步代码看起来更像是同步代码,通过暂停和恢复来表达异步操作的逻辑。

  2. 协程可以大大简化异步代码的写法,避免回调地狱(callback hell)和层层嵌套的问题。

  3. 相较于传统的基于回调的异步编程方式,协程可以更高效地利用系统资源,减少上下文切换和线程调度的开销,提高程序的性能。

  4. 协程中的暂停和恢复让状态管理非常便利,更轻松地处理异步操作中的状态和上下文切换。

在这里插入图片描述

二、C++20协程基础知识

2.1、协程的基本概念和使用方法

协程是一种轻量级线程,它可以在不同的执行上下文中暂停和恢复。在协程中,程序可以通过显式的暂停和恢复操作来控制执行流程,能够更灵活地管理并发任务。

协程的基本概念:

  1. 暂停和恢复:协程可以在执行过程中暂停自己,并在之后的某个时间点恢复执行。这种暂停和恢复是由程序员显式地控制的,可以在任何地方发生。

  2. 轻量级线程:与传统的操作系统线程相比,协程更加轻量级,可以在同一个线程中并发执行多个协程,从而减少线程切换的开销。

  3. 异步编程:协程通常用于异步编程,可以在 I/O 操作和其他耗时的任务中进行暂停和恢复,提高程序的并发性能。

在C++20中,协程使用co_awaitco_yield等关键字来实现暂停和恢复操作。通过使用co_await可以将执行权交还给调用者,同时将当前状态保存起来。而co_yield用于向调用者返回一个值,同时也会暂停当前协程的执行。协程也需要一个可调用的函数作为入口点,称为协程函数。

示例:

#include <iostream>
#include <coroutine>
/*******************************************************************
* `promise_type`结构定义协程的状态和控制逻辑,
* `initial_suspend`和`final_suspend`用于定义协程的初始化和结束时的暂停行为。
* `get_return_object`方法返回一个`Generator`对象,
* `return_void`用于处理协程返回的结果,
* `yield_value`用于返回一个值并暂停协程的执行。
********************************************************************/
struct Generator {
  struct promise_type {
    int current_value;
    std::exception_ptr exception; // 用来存储异常指针

    // 初始挂起不做任何事情
    auto initial_suspend() { return std::suspend_always{}; }
    // 最终挂起销毁coroutine
    auto final_suspend() noexcept { return std::suspend_always{}; }
    Generator get_return_object() {
      return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    // 返回时不做任何事情
    void return_void() {}
    // 挂起并记下当前value
    auto yield_value(int value) {
      current_value = value;
      return std::suspend_always{};
    }
    // 存储异常
    void unhandled_exception() { exception = std::current_exception(); }
  };

  std::coroutine_handle<promise_type> coro;

  explicit Generator(std::coroutine_handle<promise_type> h) : coro(h) {}

  ~Generator() {
    // 解构时销毁coroutine
    if (coro)
      coro.destroy();
  }

  int getValue() {
    // 无异常时返回当前值,否则重新抛出异常
    if (coro.promise().exception)
      std::rethrow_exception(coro.promise().exception);
    return coro.promise().current_value;
  }

  bool next() { 
    if (!coro.done()) {
      coro.resume();
      return !coro.done();
    }
    return false;
  }
};

Generator counter() {
  for (int i = 0; i < 5; ++i)
    co_yield i;
}

int main() {
  Generator gen = counter();
  while (gen.next()) {
    std::cout << gen.getValue() << std::endl;
  }
  return 0;
}

编译时一定要加上-std=c++20选项。

counter函数中使用co_yield将值返回给调用者,同时暂停协程的执行。在main函数通过Generator对象的next方法来依次取出协程返回的值,并输出到控制台。

这就是一般情况下C++中协程的基本概念和使用方法。

2.2、C++20中的协程支持

C++20中引入了对协程的原生支持,可以直接利用协程来编写异步程序。协程支持是通过引入一组新的关键字和库来实现的,包括co_awaitco_yieldco_return等关键字以及相关的标准库函数和类型。

关键字/库描述
co_await表示在异步操作完成前将控制权交给调用方
co_yield在协程中产生一个值并暂停执行
co_return表示协程执行结束并返回值
std::coroutine_handle一个用于控制协程句柄的类
std::suspend_always一个永远暂停的协程suspend点,通常用于展示示例以进行协程暂停和恢复的操作
std::suspend_never一个从不暂停的协程suspend点,通常用于展示示例以进行协程的初始和最终操作

C++20使用协程进行异步编程:

  1. 引入<coroutine>头文件,该头文件包括了与协程相关的标准库函数和类型;
  2. 在函数声明或定义中使用co_await关键字,表示在异步操作完成之前将控制权交给调用方;
  3. 使用co_yield关键字来在协程中产生一个值并暂停执行;
  4. 在协程的返回值上使用co_return关键字,表示协程执行结束并返回值。
执行到co_yield
执行到co_await
执行到co_return
开始
创建promise对象
创建协程对象
调用 resume() 方法
协程执行
执行到 co_yield 关键字
执行到 co_await 关键字
执行协程等待操作
等待操作完成
调用 resume() 方法继续执行
结束

示例:

#include <iostream>
#include <coroutine>

// Define a struct named Task
struct Task {
  // Define a nested struct named promise_type for Task
  struct promise_type {
    // Return a default-constructed Task object
    Task get_return_object() {
      return {};
    }
    // Suspend the coroutine indefinitely during its initial execution
    std::suspend_never initial_suspend() {
      return {};
    }
    // Suspend the coroutine indefinitely at its final execution
    std::suspend_never final_suspend() noexcept{
      return {};
    }
    // Terminate the program for unhandled exceptions
    void unhandled_exception() {
      std::terminate();
    }
    // No action for coroutines returning void
    void return_void() {}
  };

  ~Task() { 
    std::cout << "Task destroyed" << std::endl;
  }

  // Suspend the coroutine associated with the provided coroutine handle
  void await_suspend(std::coroutine_handle<promise_type>) {}
};

// Create an asynchronous function named async
Task async() {
  std::cout << "Async start" << std::endl;
  // Suspend the coroutine until being resumed (in this case indefinitely)
  co_await std::suspend_always{};
  std::cout << "Async end" << std::endl;
}

int main() {
  // Call the async function and store the resulting task
  auto task = async();
  std::cout << "Main" << std::endl;
  return 0;
}

Task是一个协程类型,通过co_await来暂停执行,并在适当的时机恢复执行。async函数是一个协程函数,其中的co_await表示在异步操作完成前暂停执行。

输出:

Async start
Main
Task destroyed

2.3、协程与传统线程的对比

调度方式:

  • 传统线程是由操作系统的调度器进行管理和调度的,它们可以并行执行在不同的物理核心上。线程的调度和切换需要内核的介入,会消耗一定的系统资源。
  • 协程是由程序员显式地控制的,它们运行在单一线程内部,并且协程之间的切换必须经过协程函数的显式调用。协程的切换不需要内核介入,并且开销较小。

内存占用:

  • 传统线程需要分配一定的内核资源来进行管理,包括线程堆栈以及线程控制块等。因此,创建大量的线程可能会占用大量的内存。
  • 协程在不同协程之间的切换通常只需要保存少量的上下文信息,所以占用的内存较少。这也是协程在高并发的场景下具有优势。

编程模型:

  • 传统线程编程通常需要使用同步原语(例如互斥锁、条件变量)来处理共享资源的并发访问,这增加了编程的复杂度。
  • 协程可以使用异步方式编写,使用协程可以更自然地进行事件驱动的编程,避免了使用传统线程编程中的锁和条件变量。

并发粒度:

  • 传统线程通常用于在多核处理器上并行执行代码,适合于CPU密集型的任务。
  • 协程通常用于处理IO密集型的任务,如网络请求,文件读写等。它可以更高效地处理大量的并发IO操作。

三、C++20协程库的实现原理

协程运行流程图:
在这里插入图片描述

在底层实现中,编译器会将协程转换为状态机。每个协程包含了一组状态(比如挂起、运行、完成等),并且通过调度器进行相互切换。当协程暂停时,其状态会被保存下来,等待下一次被唤醒时恢复执行。这种状态机的设计是实现协程的核心。

编译器会生成一些额外的代码来管理协程的状态和上下文切换。协程需要进行频繁的挂起和恢复操作,因此涉及到堆栈和上下文的保存与恢复。编译器和标准库需要在底层处理这些操作,保证协程的状态能够正确地切换和保存。

协程调度器和协程对象的概念:

  1. 协程对象代表一个具体的协程实例,它包含了协程的状态、执行过程中的数据等信息。在C++20中使用co_awaitco_yield等关键字来定义协程函数,并创建对应的协程对象。

  2. 协程调度器负责协程的调度和管理,它决定了哪个协程处于运行状态,哪个协程被挂起或恢复。

协程调度器和协程对象的关系:

  • 协程对象依赖于协程调度器进行调度和管理。当一个协程对象需要暂停或者恢复执行时,它会通过协程调度器来进行相应的操作。

  • 协程对象通过协程调度器来执行,并且协程调度器会负责协程的挂起、恢复、调度等操作。

四、C++20协程库的应用实例

在异步IO操作中使用协程简化异步操作的编写,提高代码的可读性、可维护性和性能。

  • 协程将异步IO的回调地狱转换为顺序执行的代码,代码更易于理解和编写。
  • 在异步IO场景下,协程的轻量级和低开销的优势非常明显,可以降低上下文切换的开销,提高异步IO操作的性能和响应速度。
  • 自定义的协程调度器可以与异步IO操作结合,实现灵活的异步IO调度和管理。
  • C++20协程可以与现有的异步IO框架(如Boost.Asio、libuv等)结合,利用协程改善异步IO编程。

示例:使用C++20协程库(搭配Boost.Asio)来进行异步网络通信。

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <coroutine>
#include <boost/asio/awaitable.hpp>

namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;

class AsyncTCPClient {
public:
    AsyncTCPClient(net::io_context& io_context)
        : resolver_(io_context), socket_(io_context) {}

    // Asynchronous connection to the server
    net::awaitable<void> connect(const std::string& host, unsigned short port) {
        auto results = co_await resolver_.async_resolve(host, std::to_string(port), net::use_awaitable);
        co_await net::async_connect(socket_, results, net::use_awaitable);
    }

    // Asynchronous write operation
    net::awaitable<void> write(const std::string& data) {
        co_await net::async_write(socket_, net::buffer(data), net::use_awaitable);
    }

    // Asynchronous read operation
    net::awaitable<std::string> read(int max_length) {
        std::string data;
        data.resize(max_length);
        
        size_t read_length = co_await socket_.async_read_some(net::buffer(data), net::use_awaitable);
        co_return data.substr(0, read_length);
    }

private:
    tcp::resolver resolver_;
    tcp::socket socket_;
};

int main() {
    net::io_context io_context;

    AsyncTCPClient client(io_context);

    co_spawn(io_context, [&client]() -> net::awaitable<void> {
        try {
            co_await client.connect("www.baidu.com", 80);
            co_await client.write("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n");
            std::string response = co_await client.read(1024);
            std::cout << "Received: " << response << std::endl;
        } catch (std::exception& e) {
            std::cerr << "Exception: " << e.what() << std::endl;
        }
    }, net::detached);

    io_context.run();

    return 0;
}

要先安装boost库。

sudo apt-get install libboost-all-dev

编译:

g++ -std=c++20 -lboost_system -lboost_coroutine -lboost_context -pthread testp.cc -o testp

输出:

Received: HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 9508
Content-Type: text/html
Date: Sun, 07 Jan 2024 06:15:56 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BAIDUID=84A68B6C381FF605A97D9FCB3889B2E7:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=84A68B6C381FF605A97D9FCB3889B2E7; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1704608156; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BAIDUID=84A68B6C381FF6058825F825739832C9:FG=1; max-age=31536000; expires=Mon, 06-Jan-25 06:15:56 GMT; domain=.baidu.com; path=/; version=1; comment=bd
Traceid: 1704608156352853428210666740912840254115
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
Connection: close

<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="te

总结

未来的C++标准可能会继续完善和扩展协程库,包括新增更多的协程相关工具、函数和语法糖,来满足更广泛的并发编程需求。未来会有更好的编译器支持,包括优化协程性能、提供更丰富的调试信息等。

C++20协程库会推动标准化并发编程模式,包括并发任务调度、异步操作、协同执行等,未来会进一步整合协程库与其他标准库,比如网络库、文件系统库等,提供更完整的异步操作和并发编程支持。

除了简单的协程机制,未来也会有更丰富的并发编程模型,比如actor模型、数据流编程模型等。也会进一步优化协程的性能,包括降低协程的开销、提高协程的并发性能等,以提供更高效的并发编程支持。

参考文献:

  • 【Google “战败”后,C++20 用微软的提案进入协程时代!】
  • 【基于C++20协程库封装的开源框架】
  • 【协程 (C++20)官方文档】

在这里插入图片描述

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

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

相关文章

.NET学习教程一——.net基础定义+VS常用设置

一、定义 .NET分为.NET平台和.NET框架。 .NET平台&#xff08;厨房&#xff09;.NET FrameWork 框架&#xff08;柴米油盐酱醋茶&#xff09; .NET平台&#xff08;中国移动联通平台&#xff09;.NET FrameWork 框架&#xff08;信号塔&#xff09; .NET平台基于.NET Fra…

AutomationML 学习心得

断断续续地学习AutomationML&#xff08;下面简称AML&#xff09;&#xff0c;其内容很多。概念&#xff0c;术语与与其它建模语言有类似之处&#xff0c;也有不同。同时涉及了一大堆标准。 CAEX&#xff08;IEC 62424&#xff09;COLLADA 几何、动力学模型PLCopen XML 但是&a…

Windows下Redis5+可视化软件下载、安装和配置教程-2024年1月8日

Windows下Redis5下载、安装和配置教程-2024年1月8日 一、下载二、安装三、配置环境四、配置可视化客户端 一、下载 redis是现在是没有对win系统版进行维护的&#xff0c;这个是大神完成的&#xff0c;目前是到5版本&#xff0c;选择Redis-x64-5.0.14.1.zip点击下载 下载地址&…

Python Flask JinJa2 语法介绍与示例讲解

一、概述 Flask是一个轻量级的Python Web框架&#xff0c;支持Jinja2模板引擎。Jinja2是一个流行的Python模板引擎&#xff0c;它可以使用Flask来创建动态Web应用程序。 web 页面一般需要html、css和js&#xff0c;可能最开始学习python web的时候可能这样写&#xff1a; fr…

SpringBoot+策略模式实现多种文件存储模式

一、策略模式 背景 针对某种业务可能存在多种实现方式&#xff1b;传统方式是通过传统if…else…或者switch代码判断&#xff1b; 弊端&#xff1a; 代码可读性差扩展性差难以维护 策略模式简介 策略模式是一种行为型模式&#xff0c;它将对象和行为分开&#xff0c;将行…

4.6 BOUNDARY CHECKS

我们现在扩展了tile矩阵乘法内核&#xff0c;以处理具有任意宽度的矩阵。扩展必须允许内核正确处理宽度不是tile宽度倍数的矩阵。通过更改图4.14中的示例至33 M、N和P矩阵&#xff0c;图4.18创建了矩阵的宽度为3&#xff0c;不是tile宽度&#xff08;2&#xff09;的倍数。图4.…

服务器宕机怎么办?怎么预防宕机?

相信不少用户会听到或者在文章中提到电脑宕机或者服务器宕机&#xff0c;不少用户对宕机的意思不太理解。那么服务器宕机是什么意思&#xff1f; 宕机属于计算机的术语&#xff0c;指电脑或者服务器不能正常工作。口语中我们简单的把停掉机器叫做down机&#xff0c;转换为汉字是…

九、分布式锁 —— 超详细操作演示!!!

九、分布式锁 —— 超详细操作演示&#xff01; 九、分布式锁9.1 分布式锁的工作原理9.2 问题引入9.2.1 场景9.2.2 实现9.2.3 分析 9.3 setnx 实现方式9.3.1 原理9.3.2 实现9.3.3 问题 9.4 为锁添加过期时间9.4.1 原理9.4.2 实现9.4.3 问题 9.5 为锁添加标识9.5.1 原理9.5.2 实…

揭秘阿里自研搜索引擎 Havenask 在线检索服务

作者&#xff1a;谷深 Havenask 是阿里巴巴智能引擎事业部自研的开源高性能搜索引擎&#xff0c;深度支持了包括淘宝、天猫、菜鸟、高德、饿了么在内几乎整个阿里的搜索业务。本文针对性介绍了 Havenask 的在线服务&#xff0c;它具备高可用、高时效、低成本的优势&#xff0c;…

计算机网络学习笔记(四)

文章目录 1.介绍一下HTTPS的流程。2.介绍一下HTTP的失败码。3.说一说你知道的http状态码。4. 301和302有什么区别&#xff1f;5.302和304有什么区别&#xff1f;6. 请描述一次完整的HTTP请求的过程。7.什么是重定向&#xff1f;8. 重定向和请求转发有什么区别&#xff1f;9.介绍…

电子学会C/C++编程等级考试2023年12月(四级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:移动路线 桌子上有一个m行n列的方格矩阵,将每个方格用坐标表示,行坐标从下到上依次递增,列坐标从左至右依次递增,左下角方格的坐标为(1,1),则右上角方格的坐标为(m,n)。 小明是个调皮的孩子,一天他捉来一只蚂蚁,不小心把蚂蚁…

如何让ArcGIS Pro启动显示空白页面

刚接触ArcGIS Pro的你是否会觉得在操作上有那么一些不习惯&#xff0c;从一开始软件启动就发现和ArcGIS差距很大&#xff1a;丰富的欢迎页面&#xff0c;加上默认加载的地图让你眼花缭乱&#xff0c;这里教你如何去掉这些繁杂的内容&#xff0c;还你一个干净的启动页面。 跳过…

2024年远控软件年度盘点:安全、稳定、功能之选

这目录 前言2024年热门远控软件ToDesk向日葵TeamViewerAnyDeskSplashtopAirDroidChrome Remote DesktopMicrosoft远程桌面RayLinkParallels Access 远程控制软件如何选择&#xff1f;1、功能性2、安全性3、易用性4、稳定性 未来展望与建议结语 前言 随着信息技术不断发展&…

setup 语法糖

只有vue3.2以上版本可以使用 优点&#xff1a; 更少的样板内容&#xff0c;更简洁的代码 能够使用纯 Typescript 声明props 和抛出事件 更好的运行时性能 更好的IDE类型推断性能 在sciprt标识上加上setup 顶层绑定都可以使用 不需要return &#xff0c;可以直接使用 使用组件…

java多线程-实现多线程(一)

目录 1.1 进程 1.2 线程 1.3 多线程的实现方式 ​编辑 方式1&#xff08;继承Thread类&#xff09; 1.1 进程 是正在运行的程序是系统进行资源分配和调用的独立单位每一个进程都有它自己的内存空间和系统资源 1.2 线程 线程是进程中的单个顺序控制流&#xff0c;是一条执…

802.1X(HCIP)

目录 一、802.1X协议概述 1、802.1X协议概述 2、802.1X基本概念 认证模式 认证方式 端口控制方式 3、802.1X认证触发机制 客户端主动触发 设备端主动触发&#xff08;用于支持不能主动发送EAPOL-Start报文的客户端&#xff09; 4、EAP体系结构 5、EAP报文封装结构 6…

基于Django框架的旅游推荐系统构建-计算机毕业设计源码82884

摘 要 随着社会的快速发展和人们生活水平的不断提高&#xff0c;旅游已逐渐成为人们生活的重要组成部分&#xff0c;用户能够获取旅游信息的渠道也随信息技术的广泛应用而增加。大量未经过滤的信息在展示给用户的同时&#xff0c;也淹没了用户真正感兴趣的信息。为了方便用户快…

05、Kafka ------ 各个功能的作用解释(主题和分区 详解,用命令行和图形界面创建主题和查看主题)

目录 CMAK 各个功能的作用解释&#xff08;主题&#xff09;★ 主题★ 分区★ 创建主题&#xff1a;★ 列出和查看主题 CMAK 各个功能的作用解释&#xff08;主题&#xff09; ★ 主题 Kafka 主题虽然也叫 topic&#xff0c;但它和 Pub-Sub 消息模型中 topic 主题及 AMQP 的 t…

通义千问协助分析openHarmony内核编译故障记录

drivers/hdf/khdf/manager/../../../..//framework/utils/src/hdf_sbuf.c:271:6: 错误&#xff1a; ‘-mgeneral-regs-only’ is incompatible with floating-point argument 这个编译错误提示指出&#xff0c;在编译源文件 "hdf_sbuf.c"&#xff08;位于 "driv…

【UE Niagara学习笔记】03 - 火焰喷射效果

目录 效果 步骤 一、创建粒子系统 二、制作火焰动画 三、改为GPU粒子 四、循环播放粒子动画 五、火焰喷射效果雏形 六、火焰颜色 效果 步骤 一、创建粒子系统 1. 新建一个Niagara系统&#xff0c;选择模板 命名为“NS_Flame_Thrower”&#xff08;火焰喷射&#…