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

news2024/11/24 4:45:17

协程结构体定义

之前我们使用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.coloradmin.cn/o/1649411.html

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

相关文章

【iOS】——浅析CALayer

文章目录 一、CALayer介绍二、UIview与CALayer1.区别2.联系 三、CALayer的使用1.初始化方法2.常用属性 四.CALayer坐标系1.position属性和anchorPoint属性2.position和anchorPoint的关系3.position、anchorPoint和frame的关系 五、CALayerDelegate六、CALayer绘图机制1.绘图流程…

官方教程来啦!上手体验YashanDB主备部署、同步延迟和自动切换能力

在上一篇深度干货 | 如何兼顾性能与可靠性&#xff1f;一文解析YashanDB主备高可用技术中&#xff0c;我们深入探讨了YashanDB高可用的架构设计原理和关键技术&#xff0c;本文将聚焦于实践操作&#xff0c;快速体验YashanDB的主备高可用能力。 概要 YashanDB提供了不同部署形…

C++程序设计教案

文章目录&#xff1a; 一&#xff1a;软件安装环境 第一种&#xff1a;vc2012 第二种&#xff1a;Dev-C 第三种&#xff1a;小熊猫C 二&#xff1a;语法基础 1.相关 1.1 注释 1.2 换行符 1.3 规范 1.4 关键字 1.5 ASCll码表 1.6 转义字符 2.基本框架 2.1 第一种&…

如果insightface/instantID安装失败怎么办(关于InsightFaceLoader_Zho节点的报错)

可能性有很多&#xff0c;但是今天帮朋友解决问题的时候又收集了一种新的思路。 首先&#xff0c;可以先按照这篇文章里边提到的方法去安装&#xff1a; 【全网最详细】ComfyUI下&#xff0c;Insightface安装指南-聚梦小课堂_insightface如何安装-CSDN博客 其次&#xff0c;…

解决Python中的 `ModuleNotFoundError: No module named ‘fcmeans‘` 错误

ModuleNotFoundError: No module named fcmeans 解决Python中的 ModuleNotFoundError: No module named fcmeans 错误如何解决这个错误fcmeans 库简介应用实例 解决Python中的 ModuleNotFoundError: No module named fcmeans 错误 在进行数据科学或机器学习项目时&#xff0c;…

Linux内核之获取文件系统超级块:sget用法实例(六十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

大眼橙C1 Air投影仪:千元预算内的明智之选

在科技日新月异的今天&#xff0c;投影仪已经不再是会议室或教室的专属&#xff0c;而是越来越多地走入了寻常百姓家。家庭影院的概念越来越流行&#xff0c;尤其在都市人之间逐渐成为一股风尚。市场上投影仪非常多&#xff0c;如何选到一台合适的投影仪也成为困扰广大用户的一…

了解TMS运输管理系统,实现物流高效运转

TMS运输管理系统&#xff08;Transportation Management System&#xff09;是一种集成物流和信息技术的解决方案&#xff0c;通过优化运输流程、实时跟踪货物信息和自动化管理操作&#xff0c;提高物流效率&#xff0c;降低运营成本&#xff0c;实现高效运输。 TMS运输管理系…

软件设计师-重点的构造型设计模式

一、桥接模式&#xff08;Bridge&#xff09;&#xff1a; 意图&#xff1a; 将抽象部分与其实现部分分离&#xff0c;使它们都可以独立地变化。 结构&#xff1a; 适用性&#xff1a; 不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如&#xff0c;这种情况可能是…

探索大模型能力--prompt工程

1 prompt工程是什么 1.1 什么是Prompt&#xff1f; LLM大语言模型终究也只是一个工具&#xff0c;我们不可能每个人都去训一个大模型&#xff0c;但是我们可以思考如何利用好大模型&#xff0c;让他提升我们的工作效率。就像计算器工具一样&#xff0c;要你算10的10倍&#x…

【计算机网络】计算机网络的性能指标

计算机网络的性能指标被用来从不同方面度量计算机网络的性能。常用的八个计算机网络性能指标&#xff1a;速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率、丢包率。 一.速率 (1) 数据量 比特&#xff08;bit&#xff0c;记为小写b&#xff09;是计算机中数据量的基…

JavaWEB 框架安全:Spring 漏洞序列.(CVE-2022-22965)

什么叫 Spring 框架. Spring 框架是一个用于构建企业级应用程序的开源框架。它提供了一种全面的编程和配置模型&#xff0c;可以简化应用程序的开发过程。Spring 框架的核心特性包括依赖注入&#xff08;Dependency Injection&#xff09;、面向切面编程&#xff08;Aspect-Or…

c++ 线程交叉场景试验

1.需求 1.处理一个列表的数据&#xff0c;要求按照列表的数据处理10个数据 2.可以使用多线程处理&#xff0c;但是针对每个线程&#xff0c;1~10的处理顺序不能变。 3.每个数据的处理必须原子&#xff0c;即只有一个线程可以针对某个数据进行处理&#xff0c;但是10个数据是可…

2024年CSC公派联合培养博士项目申报即将开始~

一、选派计划 联合培养博士研究生面向全国各博士学位授予单位选拔。 联合培养博士研究生的留学期限、资助期限为6-24个月。留学期限应根据拟留学单位学制、外方录取通知&#xff08;或正式邀请信&#xff09;中列明的留学时间确定。个人申报的资助期限应不超过留学期限&#…

79、贪心-跳跃游戏II

思路&#xff1a; 首先理解题意&#xff1a;从首位置跳最少多少次到达末尾。 第一种&#xff1a;使用递归&#xff0c;将所有跳转路径都获取到进行求出最小值。 第二种&#xff1a;使用动态规划&#xff0c;下一次最优取决上一次的最优解 第三针&#xff1a;贪心&#xff…

python数据分析常用基础语法

Python语言基础——语法基础 前言一、变量的介绍与使用变量的介绍变量命名规则变量的使用拓展 二、标识符标识符命名命名规则注意事项 三、数据类型数据类型的介绍数据类型的查看示例 四、输入与输出输入和输出的介绍format格式化输出占位符 五、代码缩进与注释代码缩进 前言 …

Spring Cloud 整合Sentinel

1、引入依赖 版本说明 alibaba/spring-cloud-alibaba Wiki GitHub 父pom <spring.cloud.version>Hoxton.SR12</spring.cloud.version> <spring.cloud.alibaba.version>2.2.10-RC1</spring.cloud.alibaba.version>Sentinel应用直接引用starter <…

0508_IO2

练习&#xff1a; 将一张图片修改为德国国旗 1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 #include <sys/types.h>5 #include <unistd.h>6 #include <sys/stat.h>7 #include <fcntl.h>8 #include <pthrea…

Codigger:Web应用赋能的分布式操作系统让用户卓越体验

Codigger&#xff0c;作为一个分布式操作系统&#xff0c;其独特之处在于其采用的浏览器/服务器&#xff08;Browser/Server&#xff0c;简称B/S&#xff09;架构。这种架构的核心思想是&#xff0c;通过浏览器来进入工作界面&#xff0c;页面交互部分事务逻辑在前端&#xff0…

1-1ARM开发环境搭建(GD32)

1:安装MDK最好是5.27以及以上版本&#xff0c;避免后续学习中出现相关错误 2&#xff1a;安装芯片支持包 双击安装即可&#xff0c;也可以是默认路径&#xff0c;也可以自己更改路径 3&#xff1a;安装jlink下载器驱动&#xff08;下载调试器&#xff09; 具体安装步骤如下所示…