【C++学习(35)】在Linux中基于ucontext实现C++实现协程(Coroutine),基于C++20的co_await 协程的关键字实现协程

文章目录

  • 为什么使用协程
  • 协程的理解
  • 协程优势
  • 协程的原语操作
    • yield 与 resume 是一个switch操作(三种实现方式):
  • 基于 ucontext 的协程
    • 基于 XFiber 库的操作
      • 1 包装上下文
      • 2 XFiber 上下文调度器
        • 2.1 CreateFiber
        • 2.2 Dispatch
  • 基于C++20的co_return 协程的关键字实现协程
  • 参考

为什么使用协程

从性能方面来看,对于使用异步 io 的线程,存在三个问题:

  • 系统线程占用大量的内存空间
  • 线程切换占用大量的系统时间
  • 为了线程安全,线程间需要加锁保护资源,降低执行的效率

从编程角度来看,无论同步还是异步编程方式,都是基于事件驱动的。事件驱动流程包括注册事件,绑定回调,触发回调,提高了系统的并发。但是由于回调的多层嵌套,使得编程复杂,降低了代码的可维护性。

在资源有限的前提下,高性能服务需要解决的问题有:

  • 减少线程的重复高频创建:线程池
  • 尽量避免线程的阻塞
  • Reactor + 非阻塞回调:解决问题的能力有限
  • 响应式编程:容易陷入回调地狱,割裂业务逻辑
  • 协程:将同 io 转成异步 io
  • 提升代码的可维护与可理解性:减少回调函数,减少回调链深度

而协程的出现,可以很好地解决上述问题。

协程的理解

协程(Coroutine)是一种能够挂起个恢复的函数过程 是一种轻量级的并发编程方式,也称为用户级线程。它与传统的线程(Thread)相比,具有更低的开销和更高的执行效率。 协程通常运用在异步调用中。

协程运行在线程之上。当一个协程调用阻塞 io,主动让出 cpu ( yield 原语) ,让另一个协程运行在当前线程之上( resume 原语)。协程没有增加线程数量,只是在线程的基础上通过分时复用的方式运行多个协程,降低了系统内存。而且协程的切换在用户态完成,减少了系统切换开销。

协程优势

消耗系统资源和切换代价更小
协程可以实现无锁编程
简化了异步编程,可以达到以同步的编程方式实现异步的性能。

  • 协程适用于 I/O 密集型业务,线程切换频繁。其他情况,性能不会有太大的提升。

协程的原语操作

  • yield: 协程主动让出CPU给调度器。时机:业务提交 -> epoll_wait
  • resume: 调度器恢复协程的运行权。时机:epoll_wait -> 业务处理
  • resume 和 yield 是两个可逆的原子操作。

yield 与 resume 是一个switch操作(三种实现方式):

  • 1.longjump/setjump
  • 2.ucontext
  • 3.汇编实现

基于 ucontext 的协程

协程的实现与线程的主动切换有关,当“当前上下文”可能阻塞时,需要主动切换到其它上下文来避免操作系统将当前线程挂起从而降低效率。

在Linux中定义了ucontext_t结构体来表示线程的上下文结构。

typedef struct ucontext_t {struct ucontext_t *uc_link;//表示当当前上下文阻塞时会被切换的上下文。sigset_t           uc_sigmask;//被当前线程屏蔽的信号stack_t 					 uc_stack;//线程栈mcontext_t 				 uc_mcontext;//与机器相关的线程上下文的表示
} ucontext_t;

与上下文相关的有四个函数:

    getcontext(ucontext_t* ucp): 调用后基于当前上下文初始化ucp所指向的上下文结构体。setcontext(const ucontext_t* ucp): 切换到ucp所指向的上下文,如果调用成功则不会返回,因为上下文已经被切换。makecontext(ucontext_t* ucp, void (*func)(), int argc, ...): 用于指定上下文需要执行的函数,要求在调用之前context已经确定栈和 uc_link. 当切换到该上下文后,函数func就会被执行。函数返回后,后继线程就会被切换到,如果uc_link为NULL,则线程退出。swapcontext(ucontext_t* restrict oucp, const ucontext_t* restrict ucp): 将当前上下文保存到oucp中,然后切换到ucp对应的上下文中。与setcontext的区别在于是否保存当前上下文。

附上stack_t的定义:

typedef struct {void* ss_sp;int ss_flags;size_t ss_size;
} stack_t;

比如可以通过下面的程序实现循环打印:

#include <ucontext.h>
#include <unistd.h>
#include <stdio.h>int main() {int idx = 0;ucontext_t ctx1;getcontext(&ctx1);printf("%d\n", idx);idx++;sleep(1);setcontext(&ctx1);return 0;
}

基于 XFiber 库的操作

https://github.com/HiYx/xfiber

以XFiber为例讲解一下一个轻量级协程库的基本实现方式。

1 包装上下文

Linux提供的协程结构体比较简陋,并不足以供协程库使用,因此需要进行包装一下。

struct Fiber {uint64_t seq_;XFiber* xfiber_;std::string fiber_name_;ucontext_t ctx_;uint8_t* stack_ptr_;size_t stack_size_;std::function<void()> run_;WaitingEvents waiting_events_;
};

其中XFiber为协程的调度器,后面会讲。WatingEvents为协程所需要等待的读和写的文件描述符。

struct WaitingEvents {std::vector<int> waiting_fds_w_;std::vector<int> waiting_fds_r_;int64_t expire_at_;
};

2 XFiber 上下文调度器

2.1 CreateFiber

先从最基本的创建一个协程开始,首先注意协程和线程的区别,协程代表一段可以分开执行的逻辑,但是和其它协程还是保持串行执行,因此协程创建并不会马上执行,而是由协程调度器统一执行。

先看看创建协程函数的签名:

void XFiber::CreateFiber(std::function<void()> run, size_t stack_size, std::string fiber_name);

run即要执行的函数,这里作者设定了只能是无参数、无返回值的函数类型,但是其实可以借助C++模板实现各种类型函数的注册。

协程调度器主要维护两个协程队列,分别是运行队列和就绪队列,运行队列中的协程会被切换到,而就绪队列中的协程会在下一次的循环中被切换到。

同时维护两个map,io_waiting_fibers_表示监听的文件描述符所对应的一对读和写的协程,expire_fibers_的value为一个有序集合,表示在某个时间点会超时的协程集合。

2.2 Dispatch

当Dispatch函数开始运行时,各协程才开始运行。该函数主要分为三个部分:

  • 处理已经就绪的协程。将就绪队列move到运行队列中,然后将就绪队列清空,这样做的原因是这一循环的就绪队列在运行中可能重新回到就绪队列中(主动Yield就会回到就绪队列)。

  • 协程切换过程就涉及到上面的swapcontext函数,为了使得协程能够在返回后能够重新回到XFiber中,其结构体中维护了一个sched_ctx_成员表示调度器的上下文。因此每一条Fiber在被创建时都将sched_ctx_作为接下来切换到的上下文,这样就保证了每一条协程在执行完成以后都能过回到调度器来,并由调度器处理接下来的就绪协程。

  • 检查超时的协程,对于超时的协程集合,需要将这些协程通过 WakeupFiber 函数进行唤醒。

  • 调用 epoll 相关方法,检查所有的epoll事件,并唤醒相关协程。

基于C++20的co_return 协程的关键字实现协程

co_return :co_return 是 C++20 中引入的关键字,用于在协程中返回结果或结束协程。它用于替代 return 关键字,在协程函数中表示返回值,并触发协程的完成。

main函数创建了一个进程, 进程里面创建了一个主线程,然后执行每个函数就是子线程。

  • 先进入bar()函数, 先执行call bar ,
  • 然后执行before bar 经过挂起点然后挂起,
  • 这时候一个线程跳出了bar函数, 到main里面,
  • 另一个线程执行fool函数,
  • fool函数执行完以后, 再回到bar 函数里面继续执行

这就是本代码的大概思路

在这里插入图片描述

参考

https://blog.csdn.net/txh1873749380/article/details/134174067
https://www.cnblogs.com/kaleidopink/p/16387004.html
https://blog.csdn.net/m0_74036006/article/details/135960299

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

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

相关文章

844.比较含退格的字符串

java用 O&#xff08;1&#xff09;空间这个方法&#xff0c;容易挺多bug的… O&#xff08;1&#xff09;空间 #&#xff1a;删除前一个字符 》 从后面开始判断&#xff08;这样可以用跳过的思想&#xff09;不能使用两次 i- - 来处理 # 的操作&#xff0c;会造成误删了前面…

大数据实训室建设的必要性

一、大数据发展的背景 大数据作为当今信息技术领域的核心驱动力&#xff0c;正在深刻地改变着社会的各个方面。它不仅仅是指数据量庞大&#xff0c;更重要的是指数据的多样性、实时性和复杂性。随着云计算、物联网等技术的迅猛发展&#xff0c;大数据已成为推动经济社会发展的…

数据结构------队列(Java语言描述)

一、队列的概念 队列是一种数据结构&#xff0c;它遵循先进先出的原则。就像排队买东西一样&#xff0c;先到的人先得到服务&#xff0c;先进入队列的数据元素先被取出。例如&#xff0c;在一个银行排队系统中&#xff0c;顾客按照到达的先后顺序排队等待办理业务。第一个进入…

SD外向交货单

1、写在前面 1.1、文档说明 本文档将介绍外向交货单的创建、修改、删除、过账以及冲销&#xff0c;并且包含如何使用代码来实现上述过程。另外&#xff0c;还将介绍衍生出的交货单屏幕增强和BAPI维护扩展字段等内容。 1.2、关于外向交货单 在 SAP系统中&#xff0c;外向交货…

请解释Python中的装饰器是什么?如何使用它们?

1、请解释Python中的装饰器是什么&#xff1f;如何使用它们&#xff1f; 在Python中&#xff0c;装饰器是一个接受一个函数作为参数的可调用对象&#xff08;通常是一个函数&#xff09;。装饰器本质上是一个高阶函数&#xff0c;因为它接受一个函数作为参数并返回一个新的函数…

MyBatis——增删查改(XML 方式)

1. 查询 1.1. 简单查询 使用注解的方式主要是完成一些简单的增删查改功能&#xff0c;如果要实现复杂的 SQL 功能&#xff0c;还是建议使用 XML 来配置映射语句&#xff0c;将 SQL 语句写在 XML 配置文件中 如果要操作数据库&#xff0c;需要做以下的配置&#xff0c;与注解…

K8S如何基于Istio实现全链路HTTPS

K8S如何基于Istio实现全链路HTTPS Istio 简介Istio 是什么?为什么选择 Istio?Istio 的核心概念Service Mesh(服务网格)Data Plane(数据平面)Sidecar Mode(边车模式)Ambient Mode(环境模式)Control Plane(控制平面)Istio 的架构与组件Envoy ProxyIstiod其他组件Istio 的流量管…

vue3 中那些常用 靠copy 的内置函数

文章目录 vue3 常用函数罗列总结vue3 中批量注册组件vue3 自定义指令应用define 应用补充defineComponent 不同场景应用实例 vue3 常用函数罗列总结 toRefs 用途&#xff1a; 用于将一个响应式对象&#xff08;例如reactive创建的对象&#xff09;转换为普通对象&#xff0c;其…

docker镜像源,亲测可用,时间2024-11-14

最近由于特殊原因,docker拉取镜像总是失败 如果遇到docker拉取镜像失败 可以通过添加镜像源的方式解决 vim /etc/docker/daemob.json 添加如下内容 {"registry-mirrors": ["https://docker.rainbond.cc"] } 重新加载配置 sudo systemctl daemon-reload…

什么是AIGC?分清AI技术词

目录 1.AIGC 2.About Generative AI 详细对比表格 1.AIGC 自Open-AI发布Chat-GPT以来&#xff0c;国内外各大科技公司都展开了AI军备竞赛。从过去的某一天开始&#xff0c;我们突然发现AI可以帮助我们生成文字、图片、音频、视频等内容了&#xff0c;而且开始让人难以分清背…

鸿蒙学习生态应用开发能力全景图-开发者支持平台(5)

鸿蒙相关平台作用&#xff1a;  开发者社区&#xff1a;开发者技术交流平台&#xff0c;帮助开发者探索开发实践、交流心得经验、获悉业界动态、答疑解惑。  开发者学堂&#xff1a;聚合官方鸿蒙生态课程&#xff0c;课程有慕课、微课、直播课、训练营等多种形式&#xff…

51c大模型~合集44

我自己的原文哦~ https://blog.51cto.com/whaosoft/11884382 #DR4SR 最佳学生论文解读&#xff0c;中科大、华为诺亚&#xff1a;序列推荐新范式DR4SR 本工作由认知智能全国重点实验室 IEEE Fellow 陈恩红团队与华为诺亚方舟实验室完成。陈恩红教授团队深耕数据挖掘、机器学…

uniapp中webview全屏不显示导航栏解决方案

uniapp官网文档地址&#xff1a;https://uniapp.dcloud.net.cn/api/window/window.html#getappwebview <template><view class"index"><u-navbar :is-back"true" title"标题"" :title-width"650"></u-navb…

Go语言中的`io.Pipe`:实现进程间通信的利器

在Go语言中&#xff0c;io.Pipe提供了一种在同一个进程中模拟管道&#xff08;pipe&#xff09;的方式&#xff0c;使得我们可以像操作操作系统的管道一样&#xff0c;在不同的goroutine之间进行数据传递。本文将深入探讨io.Pipe的工作原理、使用方法及其在实际开发中的应用场景…

openwebui二改界面环境搭建

1、下载源码 https://github.com/open-webui/open-webui 2、编译前端 npm i npm run dev 注意版本要求&#xff1a; Python Version: Python 3.11Node.js Version: 20.10 浏览器访问&#xff1a;http://localhost:5173/ 3、编译后端 cd backend conda create --name op…

HCIP-快速生成树RSTP

一、RSTP是什么 STP&#xff08;Spanning Tree Protocol &#xff09;是生成树协议的英文缩写。该协议可应用于环路网络&#xff0c;通过一定的算法实现路径冗余&#xff0c;同时将环路网络修剪成无环路的树型网络&#xff0c;从而避免报文在环路网络中的增生和无限循环。 RS…

多进程/线程并发服务器

多进程&#xff1a; #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <unistd.h> #include <stdlib.h>#define SER_PORT…

在Element Ui中支持从系统粘贴版中获取图片和PDF,Docx,Doc,PPT等文档

在上一篇中&#xff0c;我们单纯的实现了Ctrl V实现从粘贴版中获取图片信息&#xff0c;但是点击上传的时候会有个bug&#xff0c;就是点击文件上传的时候&#xff0c;会出现一个bug&#xff0c;这篇&#xff0c;我们将在上一篇的基础上进行完善&#xff0c;并支持从粘贴版中获…

《数据可视化技术》上机报告

一、实验目的及要求 掌握pyecharts数据可视化环境搭建以及pyecharts交互式基础图形的绘制。 &#xff08;1&#xff09;掌握pyecharts中初始配置项&#xff0c;系列配置项&#xff0c;全局配置项的配置方法。 &#xff08;2&#xff09;掌握pyecharts中条形图的绘制方法。 …

️虚拟机配置NAT和Bridge模式

虚拟机的网络配置 桥接 通过使用物理机网卡 具有单独ip NAT 把物理机为路由器进行上网 NAT模式&#xff1a; 所谓nat模式&#xff0c;就是虚拟系统会通过宿主机的网络来访问外网&#xff0c;而这里的宿主机相当于有两个网卡&#xff0c;一个是真实网卡&#xff0c;一个是虚拟…