C++手写协程项目(协程实现线程结构体、线程调度器定义,线程挂起函数、线程切换函数、线程恢复函数、线程结束函数、线程结束判断函数,模块测试)

协程结构体定义

之前我们使用linux下协程函数实现了线程切换,使用的是ucontext_t结构体,和基于这个结构体的四个函数。现在我们要用这些工具来实现我们自己的一个线程结构体,并实现线程调度和线程切换、挂起。

首先我们来实现以下线程结构体:

struct thread_t {ucontext_t ctx;void (*func)();void* args;int state;char stack[1024 * 128]; //128kB栈空间
};

其中state有四种值,RUNNABLE,RUNING,SUSPEND,END,分别对应0,1,2,3,即就绪,运行,挂起、终止这四种状态,对应操作系统下一个进程执行和终止之间的三种状态。

再写一个调度的结构体

struct scheduler {ucontext_t main;std::vector<thread_t> threads;int running_thread;scheduler():running_thread(-1) {};
};

调度器需要保存主函数上下文,需要调度的线程集合threads,用一个vector实现,和当前运行线程id;运行线程id初始时赋为-1,表示无线程正在运行。

这样线程结构体和线程调度器就已经实现和完成了。

接下来我们要实现下我们自己的线程创建函数,参数为调度器scheduler,执行函数func和执行函数的参数args

int thread_create(scheduler& myscheduler, void (*func)(), void* args) {thread_t *newthread = new thread_t();newthread->ctx.uc_link = &myscheduler.main;newthread->ctx.uc_stack.ss_sp = newthread->stack;newthread->ctx.uc_stack.ss_size = 1024*128;newthread->func = func;newthread->args = args;newthread->state = 0;myscheduler.threads.push_back(*newthread);return myscheduler.threads.size() - 1;
}

首先创建一个thread_t类型变量作为新线程,将其ctx变量的后继函数设定为调度器中主函数,栈空间和栈大小设置为其默认成员变量。对应参数赋值为给定参数方便后续使用。初始状态设置为就绪态,并将其放入调度器线程集合,线程id设置为当前线程集合大小-1.

线程挂起函数

int thread_yield(scheduler& myscheduler) {if (myscheduler.running_thread == -1) return 0;myscheduler.threads[myscheduler.running_thread].state = 2;setcontext(&myscheduler.main);return 1;
}

线程挂起函数首先判断调度器中当前运行线程id是否为-1,如果是的话就直接返回0,表示协程挂起失败。否则将正在运行线程id对应到调度器中线程集合中相应下标的元素,将其值置为2(挂起),将当前上下文设置为主函数,返回1;

线程恢复运行函数

int thread_resume(scheduler& myscheduler,int threadId) {if (threadId < 0 || threadId >= myscheduler.threads.size()) return -1;if (myscheduler.threads[threadId].state == 2) {// if (myscheduler.running_thread != -1) thread_yield(myscheduler);myscheduler.running_thread = threadId;myscheduler.threads[threadId].state = 1;swapcontext(&myscheduler.main,&myscheduler.threads[threadId].ctx);} else if (myscheduler.threads[threadId].state == 0) {    // if (myscheduler.running_thread != -1) thread_yield(myscheduler);myscheduler.running_thread = threadId;myscheduler.threads[threadId].state = 1;getcontext(&myscheduler.threads[threadId].ctx);makecontext(&myscheduler.threads[threadId].ctx, myscheduler.threads[threadId].func, 1, myscheduler.threads[threadId].args);swapcontext(&myscheduler.main,&myscheduler.threads[threadId].ctx);}
}

线程恢复运行函数首先判断给定线程Id是否<0或者>调度器线程集合大小,如果是就说明不满足条件,直接返回。否则判断其状态,我们需要处理的有挂起态和就绪态两种状态,两种情况下都需要将当前运行线程(如果有的话)挂起,将需要运行的线程状态置为1。如果当前需要运行线程之前是挂起,直接切换栈空间即可。否则需要将取当前栈空间并用makecontext函数处理下,再进行切换。

线程全部结束判断函数

int scheduler_finished(scheduler& myscheduler) {for (int i = 0; i < myscheduler.threads.size(); i++) {if (myscheduler.threads[i].state != 3) return 0;}return 1;
}

判断调度器内部线程集合里线程状态是否全为0,是就说明全部执行完,返回0,否则返回1。

线程结束状态设置函数

void thread_exit() {myscheduler.threads[running_thread].state = 3;myscheduler.running_thread = -1;
}

在每个线程函数尾调用,设置该线程状态为终止,设置调度器当前运行线程id为-1

运行结果如下.

测试代码如下:

#include <iostream>
#include <ucontext.h>
#include <vector>struct thread_t {ucontext_t ctx;void (*func)();void* args;int state;char stack[1024 * 128]; //128kB栈空间
};struct scheduler {ucontext_t main;std::vector<thread_t> threads;int running_thread;scheduler():running_thread(-1) {};
};scheduler myscheduler;int thread_create(scheduler& myscheduler, void (*func)(), void* args) {thread_t *newthread = new thread_t();newthread->ctx.uc_link = &myscheduler.main;newthread->ctx.uc_stack.ss_sp = newthread->stack;newthread->ctx.uc_stack.ss_size = 1024*128;newthread->func = func;newthread->args = args;newthread->state = 0;myscheduler.threads.push_back(*newthread);return myscheduler.threads.size() - 1;
}int thread_yield(scheduler& myscheduler) {if (myscheduler.running_thread == -1) return 0;myscheduler.threads[myscheduler.running_thread].state = 2;swapcontext(&myscheduler.threads[myscheduler.running_thread].ctx, &myscheduler.main);return 1;
}void thread_exit() {myscheduler.threads[running_thread].state = 3;myscheduler.running_thread = -1;
}int thread_resume(scheduler& myscheduler,int threadId) {if (threadId < 0 || threadId >= myscheduler.threads.size()) return -1;if (myscheduler.threads[threadId].state == 2) {//if (myscheduler.running_thread != -1) thread_yield(myscheduler);myscheduler.running_thread = threadId;myscheduler.threads[threadId].state = 1;swapcontext(&myscheduler.main,&myscheduler.threads[threadId].ctx);} else if (myscheduler.threads[threadId].state == 0) {    //if (myscheduler.running_thread != -1) thread_yield(myscheduler);myscheduler.running_thread = threadId;myscheduler.threads[threadId].state = 1;getcontext(&myscheduler.threads[threadId].ctx);makecontext(&myscheduler.threads[threadId].ctx, myscheduler.threads[threadId].func, 1, myscheduler.threads[threadId].args);swapcontext(&myscheduler.main,&myscheduler.threads[threadId].ctx);}
}int scheduler_finished(scheduler& myscheduler) {for (int i = 0; i < myscheduler.threads.size(); i++) {if (myscheduler.threads[i].state != 3) return 0;}return 1;
}void thread1() {std::cout << "hello" << std::endl;thread_exit();
}void thread2() {int n = 10;thread_yield(myscheduler);while (n--)std::cout << "world" << std::endl;thread_exit();
}int main() {getcontext(&myscheduler.main);thread_create(myscheduler, &thread1, nullptr);thread_create(myscheduler, &thread2, nullptr);if (!scheduler_finished(myscheduler)) {thread_resume(myscheduler, 0);}if (!scheduler_finished(myscheduler)) {thread_resume(myscheduler, 1);}if (!scheduler_finished(myscheduler)) {thread_resume(myscheduler, 1);}return 0;
}

上面注释掉了两行代码,这两行代码如果不注释掉,就会反映出上面所写代码的一个致命问题——线程运行结束后无法自动设置状态为结束态,导致下一个线程在调用该函数的时候在该线程栈空间和主函数栈空间之间来回切换,会直接结束而不会执行线程2函数体。而且由于某些原因,其实我们只能同时运行一个线程,而无法多线程同时运行,所以挂起只能是由该线程自己主动释放的。

但是每个线程结束时都加了thread_exit之后就不会触发这个判断条件,可以正常使用了。

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

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

相关文章

Elasticsearch 数据聚合

Bucket聚合&#xff08;桶聚合&#xff09; 对文档做分组&#xff0c;aggs 按照文档字段值或日期进行分组&#xff0c;能参与分词的字段不能做聚合&#xff0c;如text类型的字段 例如&#xff1a;根据城市名称做聚合&#xff0c;也就是城市名称对数据进行分组统计。可以加qu…

如何在已经安装好的PostgreSQL14中安装uuid 扩展

当前环境 PG14.8 LINUX 8.8 存在问题&#xff1a; 开发人员问&#xff0c;PG中&#xff0c;支持 生成UUID吗&#xff0c;具体是什么&#xff0c;答&#xff0c;类似这个函数 uuid_generate_v4() 看了一下&#xff0c; select uuid_generate_v4();会报错&#xff0…

2024.4.29 Pandas day01 基础语法

pandas是python的一个数据库&#xff0c;在使用数据库的时候需要输入 import pandas as pd 引入&#xff0c; df pd.read.csv(文件路径“&#xff09;&#xff1a;这是利用pandas数据库读取CSV文件的方法&#xff0c;如果读取EXCEL文件或者其他文件&#xff0c;csv文件换成其他…

高项第四版 十大管理及49个过程【背】作业分享

项目管理 1.十大管理【背】 包括&#xff08;口诀:范进整狗子&#xff08;沟质&#xff09; 才&#xff08;采&#xff09;干成疯子&#xff08;风资&#xff09;&#xff09;: &#xff08;1&#xff09;项目整合管理:识别、定义、组合、统一和协调各项目管理过程组的各个过…

STM32F10x移植FreeRTOS

一、获取FreeRTOS源码 &#xff08;1&#xff09;登录FreeRTOS官网&#xff1a;www.freertos.org&#xff0c;下载第一个压缩包 &#xff08;2&#xff09;通过GitHub网站&#xff1a;github.com/FreeRTOS/FreeRTOS下载&#xff0c;由于该网站服务器在国外&#xff0c;所以访问…

【busybox记录】【shell指令】sort

目录 内容来源&#xff1a; 【GUN】【sort】指令介绍 【busybox】【sort】指令介绍 【linux】【sort】指令介绍 使用示例&#xff1a; 排序 - 默认排序 排序 - 检查所给文件是否已经排序 排序 - 输出已经排序过的文件&#xff0c;不会重新排序 排序 - 忽略每行前面的空…

Linux cmake 初窥【2】

1.开发背景 基于上一篇的基础上&#xff0c;再次升级 2.开发需求 基于 cmake 指定源文件目录可以是多个文件夹&#xff0c;多层目录 3.开发环境 ubuntu 20.04 cmake-3.23.1 4.实现步骤 4.1 准备源码文件 工程目录如下 顶层脚本 compile.sh 负责执行 cmake 操作&#xff0…

基于51单片机的ADC0804的电压表设计(仿真+源码+设计资料)

目录 1、前言 2、资料内容 3、仿真图 4、程序 资料下载地址&#xff1a;基于51单片机的ADC0804的电压表设计&#xff08;仿真源码设计资料&#xff09; 1、前言 最近看网上有很少的ADC0804的设计了&#xff0c;都由0809代替&#xff0c;但是有个别因为成本原因和学校课…

从 Servlet 到 SpringMvc

从 Servlet 到 SpringMvc 下图为 SpringMvc 的 DispatcherServlet 到 Servlet 的继承体系结构&#xff0c;从 HttpServletBean 开始的子类&#xff0c;便属于 Spring 的体系结构&#xff0c;Spring 框架中类似这种以 XXXBean 结尾是用于和其它框架进行整合的 JavaBean 对象&am…

Unity技术学习:渲染大量物体的解决方案,外加RenderMesh、RenderMeshInstanced、RenderMeshIndirect的简单使用

叠甲&#xff1a;本人比较菜&#xff0c;如果哪里不对或者有认知不到的地方&#xff0c;欢迎锐评&#xff08;不玻璃心&#xff09;&#xff01; 导师留了个任务&#xff0c;渲染大量的、移动的物体。 寻找解决方案&#xff1a; 当时找了几个解决方案&#xff1a; 静态批处…

硬件工程师必读:10条职业发展黄金法则!

在快速发展的科技时代&#xff0c;硬件工程师作为推动技术创新和产业升级的重要力量&#xff0c;其职业发展之路既充满挑战也蕴含无限机遇。为了在这条道路上稳步前行&#xff0c;我们首先需要了解硬件产品的研发流程。 在这个过程中&#xff0c;公司内的每个岗位都发挥着不可或…

【Linux】基础命令

常用命令及参数&#xff1a;dir表示文件夹&#xff0c;file表示文件&#xff08;file可表示其他目录下的文件&#xff09; pwd命令&#xff1b;查看当前所属文件夹&#xff08;print working directory&#xff09; ls [选项] dir&#xff1b;查看当前、指定文件夹目录内容&am…

6.移除元素

文章目录 题目简介题目解答解法一&#xff1a;双指针代码&#xff1a;复杂度分析&#xff1a; 解法二&#xff1a;双指针优化代码&#xff1a;复杂度分析&#xff1a; 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 相关的讲解&#xff01;&#x1f600; 题目简…

无卤素产品是什么?有什么作用?

无卤素产品&#xff0c;即在生产过程中完全不使用卤素元素——氟、氯、溴、碘等——的产品。 卤素元素&#xff0c;虽然在电子设备、材料等领域应用广泛&#xff0c;却也可能潜藏危害。其阻燃剂&#xff0c;一旦在产品生命周期结束后释放&#xff0c;将对土壤和水体造成污染&a…

pxe远程安装

PXE 规模化&#xff1a;可以同时装配多台服务器 自动化&#xff1a;自动安装操作系统和各种配置 不需要光盘U盘 前置需要一台PXE服务器 pxe是预启动执行环境&#xff0c;再操作系统之前运行 实验&#xff1a; 首先先关闭防火墙等操作 [rootlocalhost ~]# systemc…

普洱茶泡多少茶叶才算淡茶?

普洱茶淡茶一般放几克茶叶&#xff0c;品深茶官网根据多年专业研究与实践结果&#xff0c;制定了淡茶冲泡标准。在冲泡普洱茶淡茶时&#xff0c;茶叶的投放量是关键因素之一。淡茶冲泡标准旨在保持茶汤的清爽口感&#xff0c;同时充分展现普洱茶的独特风味。 根据《品深淡茶冲…

手动配置dns后网速变慢

之前因为自动的dns能上qq但打不开网页&#xff0c;就手动设置了一个&#xff0c;结果近些天时不时出现网页图片加载慢的问题&#xff0c;影响到我看美女图片了&#xff0c;是可忍熟不可忍 测了下网速&#xff0c;很快&#xff0c;下载上传都是三位数的&#xff0c;那显然不是网…

交易复盘-20240507

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 蔚蓝生物 (5)|[9:25]|[36187万]|4.86 百合花…

【Qt 学习笔记】Qt常用控件 | 输入类控件 | Date/Time Edit的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 输入类控件 | Spin Box的使用及说明 文章编号&#xff1…

Quora 首席执行官亚当·德安杰洛 (Adam D’Angelo) 谈论了 AI、聊天机器人平台 Poe,以及 OpenAI 为什么不是竞争对手

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…