C++20支持协程了,快来学学协程

golang作为一种后台开发语言,可以直接支持协程且语法更为简单,C++20的特性也使得C++变得更为简单和强大。下面就来谈谈协程多线程模型中内核实现线程与线程之间的调度,通常一个线程是无法从头到尾占用着 cpu 的,尤其是进行 i/o 操作时,许多的系统调用都是阻塞的,此时内核保存该线程的上下文,然后挂起该线程。当然更多时候是由于该线程的本次运行时间耗尽,只得被挂起等待 cpu 的下一次临幸。但是多线程存在两个问题,在线程数量过多时,问题被放大的尤为明显线程的上下文切换造成的开销。线程之间对资源
摘要由CSDN通过智能技术生成

golang作为一种后台开发语言,可以直接支持协程且语法更为简单,C++20的特性也使得C++变得更为简单和强大。下面就来谈谈协程
多线程模型中内核实现线程与线程之间的调度,通常一个线程是无法从头到尾占用着 cpu 的,尤其是进行 i/o 操作时,许多的系统调用都是阻塞的,此时内核保存该线程的上下文,然后挂起该线程。当然更多时候是由于该线程的本次运行时间耗尽,只得被挂起等待 cpu 的下一次临幸。

但是多线程存在两个问题,在线程数量过多时,问题被放大的尤为明显

  • 线程的上下文切换造成的开销。
  • 线程之间对资源的竞争问题。

上下文切换

上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动:

  • 挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处,
  • 在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复
  • 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。
    这里的切换有一个时间片的概念

时间片即 CPU 分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时进程还在运行,则 CPU 将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则 CPU 当即进行切换。而不会造成 CPU 资源浪费。在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。但在微观上:由于只有一个 CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。

在函数调用的时候就已经确定该函数是否会造成阻塞

在 I/O 密集型运算中,尤其是高并发时。为了保证公平,时间片的分配会越来越小,切换越发频繁。资源也就被浪费在了上下文切换中

而 cpu 密集型运算中,不需要频繁的切换线程,所以多线程是一个不错的选择

协程

为了解决 I/O 密集型运算内核在资源调度上的缺陷,所以引入了协程(coroutine)的概念。协程也被称为用户态的线程,内核态的线程调度由内核来完成。而用户态的线程的调度则交给用户来完成,也就是应用程序,也就是我们自己。我们可以实现自己的调度算法。更重要的是,即使我们有成千上万的线程,也不用担心线程切换浪费的资源问题了。

上面我们看到要实现线程的调度的关键就是上下文状态的保存。php 中,我们通过 Generator 对象来实现程序的中断与恢复。Generator 对象在程序中断时会为我们保存中断前的现场。只要有这一点,我们的应用程序就可以自己实现协程了。这里 Generator 如何保存上下文环境,是否像线程切换一样浪费资源还需要近一步了解。

我们可以用协程实现一个支持高并发的 web 服务器,如图
在这里插入图片描述

我们将在单个进程中同时处理这些并发的请求,从 http 请求开始接手,一点一点推进,直到 response。图中的每一个线程其实就是我们所说的协程,我们要做的就是实现一个调度器,来分配上面每一个线程的运行时间。

C++中的协程

随着coroutine ts正式进入c++20,c++已经进入协程时代了。c++20提供的无栈协程,拥有许多无与伦比的优越性,比如说没有传染性,可以与以前非协程风格的代码并存,再比如说不需要额外的调度器,总之是个好东西。
但是不幸的是c++20的协程标准只包含编译器需要实现的底层功能,并没有包含简单方便地使用协程的高级库,相关的类和函数进入std标准库估计要等到c++23。所以,在c++20中,如果要使用协程,要么等别人封装好了给你用,要么就要自己学着用底层的功能自己封装。
c++的协程功能是给库的开发者使用的,所以看起来比较复杂,但是经过库的作者封装以后用起来是非常简单的,比如说asio里面就已经封装好了,相关用法看我前面的这篇文章。另外,c++的协程性能非常之高,其作者的视频介绍里面说了,(一个进程)可以开启几十亿个协程,可以说是无出其右了。
知乎上面的简单使用教程如下,大家可以简单了解一下协程在C++中的使用。
协程的出现主要是为了解决异步编程的麻烦,异步编程一般是这样的:
async_call(input1, intput2, …, call_back)
就是用一堆输入参数再加上一个回调函数作为参数,async_call函数调用后立即返回,当异步操作完成时,call_back函数会被调用。
在C++中,使用异步的场景主要有两种:

  • 需要等待的结果在其它进程中提供,比如调用操作系统的异步io读写文件、通过网络发送请求到其它进程等待处理以后结果依然通过网络返回,文件读写完成或是网络请求返回时调用指定的回调函数;
  • 需要等待的结果在本进程中提供,一般就是在别的线程中,将请求发送到别的线程,别的线程操作完成以后,调用指定的回调函数。
    不管是哪种情况,对于异步调用,都可以用协程改造成一个如下格式的“同步”调用:
    co_await coro_call(input1, input2, …)
    其实改造起来极为简单,只要在异步函数
  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++20 引入了一种新的语言特性:协程协程是一种轻量级的、非抢占式的线程,可以实现在同一个线程中切换执行不同的任务,从而避免了线程切换的开销。C++20 中的协程可以通过 co_await 和 co_yield 等关键字来实现。 在 C++20 中创建协程的方式如下: 1. 定义协程函数,使用 co_await 来等待其他协程或异步操作完成: ```c++ #include <coroutine> struct task { struct promise_type { auto initial_suspend() { return std::suspend_never{}; } auto final_suspend() { return std::suspend_never{}; } auto get_return_object() { return task{}; } void return_void() {} void unhandled_exception() {} }; }; task foo() { std::cout << "start\n"; co_await std::suspend_always{}; std::cout << "end\n"; } int main() { foo(); } ``` 2. 使用 co_yield 来暂停当前协程,并返回一个值: ```c++ #include <coroutine> #include <iostream> struct generator { struct promise_type { int current_value; auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() { return std::suspend_always{}; } auto get_return_object() { return generator{this}; } auto yield_value(int value) { current_value = value; return std::suspend_always{}; } void return_void() {} void unhandled_exception() {} }; generator(promise_type* p) : coro(std::coroutine_handle<promise_type>::from_promise(*p)) {} ~generator() { if (coro) coro.destroy(); } generator(const generator&) = delete; generator& operator=(const generator&) = delete; generator(generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; } generator& operator=(generator&& other) noexcept { if (this != &other) { coro = other.coro; other.coro = nullptr; } return *this; } int next() { coro.resume(); return coro.promise().current_value; } private: std::coroutine_handle<promise_type> coro; }; generator range(int from, int to) { for (int i = from; i < to; ++i) { co_yield i; } } int main() { for (auto i : range(0, 5)) { std::cout << i << '\n'; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值