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://www.hqwc.cn/news/674780.html

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

相关文章

6.Nginx

Nginx反向代理 将前端发送的动态请求有Nginx转发到后端服务器 那为何要多一步转发而不直接发送到后端呢&#xff1f; 反向代理的好处&#xff1a; 提高访问速度&#xff08;可以在nginx做缓存&#xff0c;如果请求的是同样的接口地址&#xff0c;这样就不用多次请求后端&#…

开源go实现的iot物联网新基建平台

软件介绍 Magistrala IoT平台是由Abstract Machines公司开发的创新基础设施解决方案&#xff0c;旨在帮助组织和开发者构建安全、可扩展和创新的物联网应用程序。曾经被称为Mainflux的平台&#xff0c;现在已经开源&#xff0c;并在国际物联网领域受到广泛关注。 功能描述 多协…

JZ71 变态跳台阶

&#x1f600;前言 本文探讨了一个有关青蛙跳台阶的变体问题&#xff0c;与传统的台阶跳跃不同&#xff0c;这只青蛙每次可以跳上任意多的台阶。我们需要解决的问题是&#xff1a;对于给定的台阶数&#xff0c;计算青蛙跳上该台阶的所有可能方法。本文将通过动态规划和数学推导…

使用Java编写的简单彩票中奖概率计算器

前言 在当今社会&#xff0c;彩票已经成为许多人追逐梦想和改变生活的一种方式。然而&#xff0c;中奖的概率却是一个让人犹豫和兴奋的话题。在这篇文章中&#xff0c;我们将探讨如何使用Java编程语言实现一个简单的彩票中奖概率计算器。通过这个计算器&#xff0c;我们可以根…

【智能算法】人类进化优化算法(HEOA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;J Lian受到人类进化启发&#xff0c;提出了人类进化优化算法&#xff08;Human Evolutionary Optimization Algorithm, HEOA&#xff09;。 2.算法原理 2.1算法思想 …

欧式聚类提取-------PCL

欧式聚类 std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> PclTool::euclideanClustering(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud) {std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> clustered_clouds;// 下采样pcl::Vox…

Linux各目录及每个目录的详细介绍

目录 /bin 存放二进制可执行文件(ls,cat,mkdir等)&#xff0c;常用命令一般都在这里。 /etc 存放系统管理和配置文件 /home 存放所有用户文件的根目录&#xff0c;是用户主目录的基点&#xff0c;比如用户user的主目录就是/home/user&#xff0c;可以用~user表示 /us…

学习和分析各种数据结构所要掌握的一个重要知识——CPU的缓存利用率(命中率)

什么是CPU缓存利用率&#xff08;命中率&#xff09;&#xff0c;我们首先要把内存搞清楚。 硬盘是什么&#xff0c;内存是什么&#xff0c;高速缓存是什么&#xff0c;寄存器又是什么&#xff1f; 我们要储存数据就要运用到上面的东西。首先里面的硬盘是可以无电存储的&#…

【工作记录】openjdk-22基础镜像的构建

背景 近期使用到的框架底层都用的是springboot3.0&#xff0c;要求jdk版本在17甚至更高。 于是决定制作一个基于openjdk22的基础镜像&#xff0c;本文对这一过程进行记录。 作为记录的同时也希望能够帮助到需要的朋友。 期望效果 容器内可以正常使用java相关命令且版本是2…

《十九》Qt Http协议及实战

前言 本篇文章来给大家讲解QT中的Http协议&#xff0c;Http协议主要用于网络中数据的请求和响应&#xff0c;那么这篇文章将给大家讲解一下这个协议。 一、HTTP概述 HTTP&#xff08;超文本传输协议&#xff09;是互联网上应用最为广泛的协议之一&#xff0c;它定义了客户端…

书生·浦语大模型实战营之 OpenCompass大模型评测

书生浦语大模型实战营之 OpenCompass &#xff1a;是骡子是马&#xff0c;拉出来溜溜 为什么要研究大模型的评测&#xff1f; 百家争鸣&#xff0c;百花齐放。 首先&#xff0c;研究评测对于我们全面了解大型语言模型的优势和限制至关重要。尽管许多研究表明大型语言模型在多…

庐山西海服务区:从高速服务区到旅游热点的华丽转身

五一假期期间&#xff0c;庐山西海服务区以其独特的魅力吸引了众多游客的目光。曾经只是一个供汽车加油和休息的普通服务区&#xff0c;如今却焕发出了绚丽的光彩&#xff0c;成为了周边地区备受瞩目的旅游热点。庐山西海服务区的转型&#xff0c;不仅为游客带来了丰富多样的娱…