协程设计原理与实现
协程设计原理与汇编实现
同步与异步
对于任何一个事情,都可以划分为不同的步骤。所谓同步,就先做第一个事情,按照这件事的步骤完成这件事之后,再去做第二件事。再去做第三件事,以此类推。
异步就是,可以先开始做第一件事,从第一个步骤开始,但是当做到某个步骤之后,需要暂停等待,于是跳转到了第二件事,又开始从第二件事的第一个步骤开始做,当做到某个步骤后,又需要暂时等待,于是跳转到了第三个事件,从第一个步骤开始做,可能做到某个步骤后,发现第一个事情暂停的步骤被唤醒了,于是转过头去继续把后续的事情做完,再去看第二件事等待的资源是否完成。以此类推。
所以从宏观上(即从某个时间段)来看,同步像是分开进行的,异步像是同时执行的。这一点和操作系统中的某些概念是类似的。
什么是协程
协程的核心就是,以同步的方式,实现异步的性能。以上面的例子讲解,就是用不同的函数实现不同的步骤,这样在编程看起来是同步的(就是所谓的以同步的方式),然后不同函数在实现步骤的时候,会设定一个类似于原语的操作,如果这个步骤无法立刻完成,就跳转到下一个事情中去,直到满足条件之后,再回来继续完成该事件(这个原语即是实现了异步的性能)。
如何实现协程
第一种方式:setjmp/longjmp
首先需要定义一个 jmp_buf类型的变量env,代表此时的环境编号,类似于存档点。
setjmp函数:调用此函数,会保存当前系统的堆栈里的数据,进行存档。返回值为0,代表首次进行存档。返回值为x,代表的下一次回退到存档点应该走的路线。
longjmp函数:调用此函数,会直接回退到存档点,其中函数的第二个参数x就是上面setjmp下一次要返回的值。(即下一次要走的路线)
举例:
#include<stdio.h>
#include<setjmp.h>jmp_buf env;int fun(int arg){printf("fun %d \n", arg);arg++;longjmp(env, arg);return 0;
}int main(){int ret = setjmp(env);if(ret == 0){fun(ret);}else if(ret == 1){fun(ret);}else if(ret == 2){fun(ret);}else if(ret == 3){fun(ret);}}
弊端
如果是多线程,会出现不同的堆栈,这样在保存的时候,会出现函数未定义等情况。不建议使用
第二种方式:ucontext
ucontext相比于上一个,类似于让用户自己实现了上下文信息的保存,而不是像setjmp一样通过调用函数让系统来保存。
定义了一个ucontext的结构体来保存上下文信息。
调用getcontext(&uc)函数把上下文信息保存在uc结构体中,并且初始化该结构体(初始化的内容:分配数组、确定返回、帮定函数)
调用swapcontext(&ctx,&ctx2),切换上下文。
#include<ucontext.h>
#include<stdio.h>ucontext_t ctx[3], main_ctx;
int count = 0;void fun1(){while (count ++ < 30){printf("1\n");swapcontext(&ctx[0], &ctx[1]);printf("4\n");}
}void fun2(){while (count ++ < 30){printf("2\n");swapcontext(&ctx[1], &ctx[2]);printf("3\n");}
}void fun3(){while (count ++ < 30){printf("3\n");swapcontext(&ctx[2], &ctx[0]);printf("6\n");}
}int main(){int stack1[2048] = {0};int stack2[2048] = {0};int stack3[2048] = {0};getcontext(&ctx[0]);ctx[0].uc_stack.ss_sp = stack1;ctx[0].uc_stack.ss_size = sizeof(stack1);ctx[0].uc_link = &main_ctx;makecontext(&ctx[0], fun1, 0);getcontext(&ctx[1]);ctx[1].uc_stack.ss_sp = stack2;ctx[1].uc_stack.ss_size = sizeof(stack2);ctx[1].uc_link = &main_ctx;makecontext(&ctx[1], fun2, 0);getcontext(&ctx[2]);ctx[2].uc_stack.ss_sp = stack3;ctx[2].uc_stack.ss_size = sizeof(stack3);ctx[2].uc_link = &main_ctx;makecontext(&ctx[2], fun3, 0);printf("swapcontext\n");swapcontext(&main_ctx, &ctx[0]);printf("\n");}

我的一些理解:
在正常执行主函数调用子函数的时候,系统内部是会帮我们实现一些操作的,会保存当前堆栈的信息,然后再另外分配一个新的空间(堆栈)用来执行子函数,当子函数执行完毕再返回调用时堆栈的状态。
那么协程其实就是要求用户自己实现了这样一个过程,而不是再交给系统来做了。首先需要初始化ctx结构体,就相当于完成了调用子函数的功能,getcontext实现保存当前堆栈信息,ctx的uc_stack实现了子函数内存的分配,ctx的uc_link实现了子函数的返回地点,makecontext实现了给这个子函数命名。与调用子函数不同的点在于,可以在子函数内部使用swapcontext用于不同子函数的切换,其中内部的堆栈会记录该子函数目前执行到了哪一个步骤,继续往下执行。
从这张图其实可以看出所谓的“同步的方式,实现异步的性能”,手动模拟了调用子函数的过程,即为“同步的方式”,但是在实现子函数内部中,又允许不同子函数之间切换(蓝色是协程实现的核心),即实现了“异步的性能”。
协程实现了类似于操作系统中处理机的时间片轮转的操作。
弊端:这样如果是协程之间互相切换,不可控,于是需要实现一个调度器schedual
调度器实现的就是不让子函数之间相互切换,而是需要切换时,切到主函数中去,这样可控。以下代码中,main里面的while循环就是一个简单的调度器,子函数中swapcontext也是跳转到主函数中去。
#include<ucontext.h>
#include<stdio.h>ucontext_t ctx[3], main_ctx;
int count = 0;void fun1(){while (count ++ < 30){printf("1\n");//swapcontext(&ctx[0], &ctx[1]);swapcontext(&ctx[0], &main_ctx); //跳转到主函数中去,而不是子涵数间互转printf("4\n");}
}void fun2(){while (count ++ < 30){printf("2\n");//swapcontext(&ctx[1], &ctx[2]);swapcontext(&ctx[1], &main_ctx);printf("3\n");}
}void fun3(){while (count ++ < 30){printf("3\n");//swapcontext(&ctx[2], &ctx[0]);swapcontext(&ctx[2], &main_ctx);printf("6\n");}
}int main(){int stack1[2048] = {0};int stack2[2048] = {0};int stack3[2048] = {0};getcontext(&ctx[0]);ctx[0].uc_stack.ss_sp = stack1;ctx[0].uc_stack.ss_size = sizeof(stack1);ctx[0].uc_link = &main_ctx;makecontext(&ctx[0], fun1, 0);getcontext(&ctx[1]);ctx[1].uc_stack.ss_sp = stack2;ctx[1].uc_stack.ss_size = sizeof(stack2);ctx[1].uc_link = &main_ctx;makecontext(&ctx[1], fun2, 0);getcontext(&ctx[2]);ctx[2].uc_stack.ss_sp = stack3;ctx[2].uc_stack.ss_size = sizeof(stack3);ctx[2].uc_link = &main_ctx;makecontext(&ctx[2], fun3, 0);printf("swapcontext\n");//简单的调度实现while(count < 30){swapcontext(&main_ctx, &ctx[count%3]);}printf("\n");}
课程地址:www.github.com/0voice
相关文章:
协程设计原理与实现
协程设计原理与汇编实现 同步与异步 对于任何一个事情,都可以划分为不同的步骤。所谓同步,就先做第一个事情,按照这件事的步骤完成这件事之后,再去做第二件事。再去做第三件事,以此类推。 异步就是,可以…...
合并区间C和C++的区别、布尔、整型、浮点、指针类型和0做比较、malloc、calloc、realloc的区别
56. 合并区间 class Solution { public:vector<vector<int>> merge(vector<vector<int>>& intervals) {//先按照每个区间的左元素排序,这样每个区间的左边界就固定了,所以之后考虑相邻的//区间是否是相交的就行 类似与栈的…...
Flutter 图片编辑板(一) 事件路由
一个图片编辑板,有两部分组成。编辑板和内容项。每一个内容项是被InteractiveViewer修饰的widget,具有缩放偏移的功能。 在图片编辑板上, 会有多个内容相,图片或文字(添加文字目前还没做过)。 当要编辑其中…...
【Java】—— 图书管理系统
基于往期学习的类和对象、继承、多态、抽象类和接口来完成一个控制台版本的 “图书管理系统” 在控制台界面中实现用户与程序交互 任务目标: 1、系统中能够表示多本图书的信息 2、提供两种用户(普通用户,管理员) 3、普通用户…...
数据库基础入门:从零开始学习数据库的核心概念
数据库是现代软件开发的核心组成部分之一,无论是网站、手机应用还是企业管理系统,都离不开数据库的支持。本文将带你从零开始,逐步了解数据库的基本概念和常见操作。 什么是数据库? 数据库(Database)是一个…...
Y20030002 微信+Java+Jsp+Servlet+MySQL的问卷调查小程序的设计与实现 源代码 配置文档 全套资料
问卷调查微信小程序 1.摘要2. 系统开的背景和意义3. 国内外研究现状4. 系统功能5.界面展示6.源码获取 1.摘要 摘 要:本文深入研究并实现了一个基于微信小程序的问卷调查系统。微信小程序问卷调查系统借助于微信小程序的便捷性和普及性,为用户提供了一个…...
ros项目dual_arm_pick-place(urdf文件可视化查看)
前言 一直想写一些项目的讲解,今天(2024.12.05)可以说正式开始了。 dual_arm_pick-place项目,是关于两个机械臂协同传递物品。 正文 这次的话,给大家讲一下里面的urdf文件。 这篇文章主要来看一下项目中的urdf文件…...
AI-安全-B站
1 需求 百度-林道正-《大模型合规探索》火山引擎-林泽韬-《大模型安全挑战与防护实践》Chamd5-bayuncao-《基于RAG的AI代码审计框架》德国电信咨询有限公司-杨麟-《AI在SOC中的应用发展》360-李亚青-《以模制模,大模型安全的解决之道》金晴云华-富吉祥-《安全大脑在…...
【C#设计模式(19)——备忘录模式(MementoPattern)】
前言 备忘录模式:将想要备份的信息交给备忘录对象来管理。通过设置初始、备份、修改、恢复等状态展示备忘录模式的使用。 代码 //备忘录类 public class Memento {private string state;public string State { get>state;}public Memento(string state){this.st…...
第三部分:进阶概念 8.事件处理 --[JavaScript 新手村:开启编程之旅的第一步]
JavaScript 事件处理是 Web 开发中不可或缺的一部分,它允许开发者响应用户的交互行为(如点击、键盘输入等)或浏览器的行为(如页面加载完成)。通过事件处理,我们可以使网页更加动态和互动。以下是关于 JavaS…...
工具推荐-js爬取工具
现在测试方向都偏向于从js中的接口来入手找到可以进的点,关于快速扫描js文件来发现敏感接口的工具有很多,下面的jjjjs就是其一 项目地址: GitHub - ttstormxx/jjjjjjjjjjjjjs: 爬网站JS文件,自动fuzz api接口,指定api接口&#x…...
Android问题记录 - Inconsistent JVM-target compatibility detected for tasks
文章目录 前言开发环境问题描述问题分析解决方案补充内容最后 前言 前段时间升级Android Studio后修复了一堆问题,详情请看:Android问题记录 - 适配Android Studio Ladybug/Java 21/AGP 8.0(持续更新)。我以为问题已经全部解决了…...
ejb组件(rmi) webservice平台(xml)
springboot bean 在 Spring Boot 中,Bean 是 Spring 框架的核心概念之一,表示由 Spring 容器管理的对象。通过 Bean 或其他注解(如 Component、Service、Repository 等)来定义和管理这些对象。以下是关于 Spring Boot 中 Bean 的…...
【jvm】垃圾回收的重点区域
目录 1. 说明2. 堆(Heap)3. 方法区(Method Area) 1. 说明 1.JVM(Java Virtual Machine)垃圾回收的重点区域主要集中在堆(Heap)和方法区(Method Area)。2.堆是…...
PyQt信号槽实现页面的登录与跳转 #页面进一步优化
将登录框中的取消按钮使用信号和槽的机制,关闭界面。 将登录按钮使用信号和槽连接到自定义的槽函数中,在槽函数中判断ui界面上输入的账号是否为"admin",密码是否为"123456",如果账号密码匹配成功,当前界面关…...
谈谈web3
全面解析 Web3:未来互联网的革命性进程 引言:互联网进化的三部曲 互联网的发展经历了三个重要阶段,每一个阶段都深刻地改变了我们的生活方式: Web1(1990-2005):静态互联网时代,人…...
正则表达式实战例子
正则表达式实战例子 1. 验证电子邮件地址 定义一个合理的电子邮件格式,并检查给定的字符串是否符合这个模式。 import redef is_valid_email(email):# 定义电子邮件格式的正则表达式pattern r^[a-zA-Z0-9_.-][a-zA-Z0-9-]\.[a-zA-Z0-9-.]$return bool(re.match(…...
Hadoop不同版本的区别
免费springboot,vue,springcloudalibaba视频,有兴趣可以看看 <!-- springboot,springboot整合redis,整合rocketmq视频: --> https://www.bilibili.com/video/BV1nkmRYSErk/?vd_source14d27ec13a473…...
QtCreator UI界面 菜单栏无法输入中文
如下图红色所示的区域,直接输入是无法输入中文的: 解决方法:在右边的属性值里输入即可 也可以参考这位同学的解决方法:友情链接...
java switch及其新特性
switch是什么 在Java中,switch语句是一种多分支选择结构,它允许程序根据一个表达式的值从多个代码块中选择执行哪一个。switch语句通常比多个if-else语句更清晰、更易读。 Java switch语句的基本语法: switch (expression) {case value1:/…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
