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.rhkb.cn/news/325634.html

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

相关文章

vivado Kintex UltraScale 配置存储器器件

Kintex UltraScale 配置存储器器件 下表所示闪存器件支持通过 Vivado 软件对 Kintex UltraScale 器件执行擦除、空白检查、编程和验证等配置操作。 本附录中的表格所列赛灵思系列非易失性存储器将不断保持更新 &#xff0c; 并支持通过 Vivado 软件对其中所列非易失性存…

使用海外云手机为亚马逊店铺引流

在全球经济一体化的背景下&#xff0c;出海企业与B2B外贸企业愈发重视海外市场的深耕&#xff0c;以扩大市场份额。本文旨在探讨海外云手机在助力亚马逊店铺提升引流效果方面的独特作用与优势。 海外云手机&#xff0c;一种基于云端技术的虚拟手机&#xff0c;能够在单一硬件上…

Qt自定义控件--提升为

为什么要自定义控件 1&#xff0c;有复合小控件需要组合为一个整体控件时&#xff1b; 2&#xff0c;一个复合控件需要重复使用时&#xff1b; 实现 自定义控件文件 新增三个文件 关联不同组的控件 关联之前的准备工作 1&#xff0c;在主控件选择和子控件所有控件所在控件…

k8s概述及核心组件

一、k8s概述 1.1 引言 docker compose 单机编排工具 有企业在用 docker swarm 能够在多台主机中构建一个docker集群 基本淘汰集群化管理处理工具 容器 微服务封装 dockerfile 编写成镜像 然后进行发布 dockerfile 可以写成shell脚本&#xff08;函数做调…

提升网络性能,解决网络故障,了解AnaTraf网络流量分析仪

在当今数字化时代&#xff0c;网络性能监测与诊断(Network Performance Monitoring and Diagnosis,NPMD)成为了企业和个人关注的焦点。随着网络流量不断增长&#xff0c;确保网络的稳定性和高效性变得更加重要。在这个领域&#xff0c;AnaTraf网络流量分析仪是您不可或缺的得力…

Mysql数据库的基础学习

为什么使用数据库&#xff1f; 1.持久化&#xff1a;将数据保存到可掉电式存储设备中以供使用。 数据库相关概念&#xff1a; DB:数据库&#xff08;Databass&#xff09;即存储数据的仓库&#xff0c;本质是一个文件系统&#xff0c;保存了一系列有组织的数据DBMS:数据库管…

B端弹窗设计指南,3000字讲清楚,内附大量案例。

B端系统弹窗是指在企业级&#xff08;Business to Business&#xff09;系统中&#xff0c;弹出的窗口或对话框&#xff0c;用于向用户展示信息、提供操作选项或者收集用户输入。 一、B端系统弹窗的作用 作用如下&#xff1a; 提示和通知&#xff1a;弹窗可以用于向用户展示重…

springboot整合rabbitmq的不同工作模式理解

前提是已经安装并启动了rabbitmq&#xff0c;并且项目已经引入rabbitmq&#xff0c;完成了配置。 不同模式所需参数不同&#xff0c;生产者可以根据参数不同使用重载的convertAndSend方法。而消费者均是直接监听某个队列。 不同的交换机是实现不同工作模式的关键组件.每种交换…

最大数字——蓝桥杯十三届2022国赛大学B组真题

问题分析 这道题属于贪心加回溯。所有操作如果能使得高位的数字变大必定优先用在高位&#xff0c;因为对高位的影响永远大于对低位的影响。然后我们再来分析一下&#xff0c;如何使用这两种操作&#xff1f;对于加操作&#xff0c;如果能使这一位的数字加到9则变成9&#xff0…

使用socat做端口转发

最近买的云上mongo数据库但是数据库不支持外网访问&#xff0c;准备做iptables转发但是一直不成功&#xff0c;腾讯云官方给予的解释是受服务器内启动的docker影响 做iptables转发会冲突&#xff0c;所以只能另想办法&#xff0c;我发现使用socat做转发也很好用&#xff0c;所以…

.net 6.0 框架集成ef实战,步骤详解

一、代码框架搭建 搭建如下代码架构&#xff1a; 重点含EntityFrameworkCore工程&#xff0c;该工程中包含AppDbContext.cs和数据表实体AggregateObject 1、AppDbContext 代码案例 //AppDbContext 代码案例using Microsoft.EntityFrameworkCore;namespace EntityFrameworkCo…

OGG几何内核-网格化的改进

OGG社区于4月19日发布了OGG 1.0 preview版本。相对于OCCT 7.7.0有很多改进&#xff0c;目前在持续研究中。最近测试了一下网格化&#xff0c;确实有很好的改进。对比展示如下&#xff1a; 几何内核&#xff1a; OGG 1.0 preview 几何内核&#xff1a;OCCT 7.7.0 采用OCCT几何内…

栈与队列的实现

前言 本次博客将要实现一下栈和队列&#xff0c;好吧 他们两个既可以使用动态数组也可以使用链表来实现 本次会有详细的讲解 栈的实现 栈的基础知识 什么是栈呢&#xff1f; 栈的性质是后进先出 来画个图来理解 当然可不可以出一个进一个呢&#xff0c;当然可以了 比如…

BGP基础

1.BGP概述 &#xff08;1&#xff09;AS IANA&#xff08;Internet Assigned Numbers Authority&#xff0c;因特网地址分配组织&#xff09;&#xff1a;IAB&#xff08;Internet Architecture Board&#xff0c;因特网体系委员会&#xff09;的下设组织。IANA授权NIC&#x…

Web前端一套全部清晰 ⑧ day5 CSS.3 选择器、PxCook软件、盒子模型

谁不是一路荆棘而过呢 —— 24.5.12 CSS.3 选择器、PxCook软件、盒子模型 一、选择器 1.结构伪类选择器 1.作用: 根据元素的结构关系查找元素。 选择器 说明 E:first-child 查找第一个 E元素 E:last-child 查找最后一个E元素 E:nth-chil…

系统权限控制插件封装-实现系统权限控制插件化

背景&#xff1a;按照传统的开发方式方式&#xff0c;每次新开发一个系统&#xff0c;就需要花费大量时间精力去搭建权限控制模块&#xff0c;如果我们把权限控制这一整个模块都抽离成一个独立的权限控制插件&#xff0c;支持单命令安装&#xff0c;全面暴露参数与方法&#xf…

OpenCV下载安装教程(Windows)

一、什么是OpenCV OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个广泛使用的开源计算机视觉库&#xff0c;旨在提供丰富的图像和视频处理功能。它最初由Intel于1999年开发&#xff0c;并演变成为一个全球性的开源项目&#xff0c;得到了众多开发者…

K8s源码分析(二)-K8s调度队列介绍

本文首发在个人博客上&#xff0c;欢迎来踩&#xff01; 本次分析参考的K8s版本是 文章目录 调度队列简介调度队列源代码分析队列初始化QueuedPodInfo元素介绍ActiveQ源代码介绍UnschedulableQ源代码介绍**BackoffQ**源代码介绍队列弹出待调度的Pod队列增加新的待调度的Podpod调…

cmd输入mysql -u root -p无法启动

问题分析&#xff1a;cmd输入mysql -u root -p无法启动 解决方法&#xff1a;配置系统环境变量 1.找到mysql安装文件下的bin文件&#xff1a;&#xff08;复制改文件地址,如下图所示&#xff09; 2.电脑桌面下方直接搜索环境变量并进入&#xff0c;如下图 3.点击环境变量&a…

Python 中的 Lambda 函数:简单、快速、高效

大家好&#xff0c;今天再给大家介绍一个python的一个强大工具Lambda 函数&#xff0c;它允许你快速定义简单的匿名函数。这种函数是“匿名的”&#xff0c;因为它们不需要像常规函数那样被明确命名。 在本文中&#xff0c;我们将通过清晰的解释和实用的示例&#xff0c;深入了…