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用于向调用者返回一个值,同时也会暂停当前协程的执行。协程也需要一个可调用的函数作为入口点,称为协程函数。

示例:

展开

代码语言:C++

自动换行

AI代码解释


#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关键字,表示协程执行结束并返回值。

示例:

展开

代码语言:JavaScript

自动换行

AI代码解释


#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表示在异步操作完成前暂停执行。

输出:

代码语言:JavaScript

自动换行

AI代码解释


Async start Main Task destroyed

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

调度方式:

  • 传统线程是由操作系统的调度器进行管理和调度的,它们可以并行执行在不同的物理核心上。线程的调度和切换需要内核的介入,会消耗一定的系统资源。

  • 协程是由程序员显式地控制的,它们运行在单一线程内部,并且协程之间的切换必须经过协程函数的显式调用。协程的切换不需要内核介入,并且开销较小。

内存占用:

  • 传统线程需要分配一定的内核资源来进行管理,包括线程堆栈以及线程控制块等。因此,创建大量的线程可能会占用大量的内存。

  • 协程在不同协程之间的切换通常只需要保存少量的上下文信息,所以占用的内存较少。这也是协程在高并发的场景下具有优势。

编程模型:

  • 传统线程编程通常需要使用同步原语(例如互斥锁、条件变量)来处理共享资源的并发访问,这增加了编程的复杂度。

  • 协程可以使用异步方式编写,使用协程可以更自然地进行事件驱动的编程,避免了使用传统线程编程中的锁和条件变量。

并发粒度:

  • 传统线程通常用于在多核处理器上并行执行代码,适合于CPU密集型的任务。

  • 协程通常用于处理IO密集型的任务,如网络请求,文件读写等。它可以更高效地处理大量的并发IO操作。


 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐