基于ucontext库实现协程类

文章目录

  • 前言
  • 协程基础知识
    • 协程上下文
    • 对称协程与⾮对称协程
    • 有栈协程与⽆栈协程
  • ucontext库
    • 接口熟悉
    • 一个简单的函数切换
    • 自动调用
  • 协程类的实现
    • 接口
    • 全局变量
    • 线程局部变量
    • malloc封装
    • 协程切换
    • 构造函数
    • 协程执行的方法
  • 测试
    • 协程切换
    • 手动切换+复用


前言

    协程(Coroutine)是一种"轻量级线程, 用户态线程",允许在执行过程中暂停和恢复执行,从而实现更加灵活的控制流程。与线程不同,协程在用户空间由程序自身调度,不需要操作系统的调度器介入, 我们使用ucontext实现一个简单的协程类

协程基础知识

协程上下文

    协程则可以执⾏到⼀半就退出(称为yield),但此时协程并未真正结束,只是暂时让出CPU执⾏权,在后⾯适当的时机协程可以重新恢复运⾏(称为resume),在这段时间⾥其他的协程可以获得CPU并运⾏, 协程能够半路yield、再重新resume的关键是协程存储了函数在yield时间点的执⾏状态,这个状态称为协程上下⽂

对称协程与⾮对称协程

    对称协程,协程可以不受限制地将控制权交给任何其他协程。任何⼀个协程都是相互独⽴且平等的,调度权可以在任意协程之间转移。于是调度起来可以这样, 协程1,协程2, 协程3之间是可以通过协程调度器可以切换到任意协程。
在这里插入图片描述

    ⾮对称协程,是指协程之间存在类似堆栈的调⽤⽅-被调⽤⽅关系。协程出让调度权的⽬标只能是它的调⽤者, 切换线程必须先回到主协程
在这里插入图片描述

有栈协程与⽆栈协程

  • 有栈协程:⽤独⽴的执⾏栈来保存协程的上下⽂信息。当协程被挂起时,栈协程会保存当前执⾏状态(例如函数调⽤栈、局部变量等),并将控制权交还给调度器。当协程被恢复时,栈协程会将之前保存的执⾏状态恢复,从上次挂起的地⽅继续执⾏。类似于内核态线程的实现,不同协程间切换还是要切换对应的栈上下⽂,只是不⽤陷⼊内核⽽已
  • ⽆栈协程:它不需要独⽴的执⾏栈来保存协程的上下⽂信息,协程的上下⽂都放到公共内存中,当协程被挂起时,⽆栈协程会将协程的状态保存在堆上的数据结构中,并将控制权交还给调度器。当协程被恢复时,⽆栈协程会将之前保存的状态从堆中取出,并从上次挂起的地⽅继续执⾏。协程切换时,使⽤状态机来切换,就不⽤切换对应的上下⽂了,因为都在堆⾥的。⽐有栈协程都要轻量许多

ucontext库

    ucontext是GNU C库提供的⼀组创建,保存,切换⽤户态执⾏上下⽂的接口,我们可以使用ucontext库切换和恢复协程

接口熟悉

typedef struct ucontext_t {// 当前上下⽂结束后,下⼀个激活的上下⽂对象的指针,只在当前上下⽂是由makecontext创建时有效struct ucontext_t *uc_link;// 当前上下⽂的信号屏蔽掩码sigset_t uc_sigmask;// 当前上下⽂使⽤的栈内存空间,只在当前上下⽂是由makecontext创建时有效stack_t uc_stack;// 平台相关的上下⽂具体内容,包含寄存器的值mcontext_t uc_mcontext;...
} ucontext_t;
// 获取当前的上下⽂
int getcontext(ucontext_t *ucp);
// 恢复ucp指向的上下⽂,这个函数不会返回,⽽是会跳转到ucp上下⽂对应的函数中执⾏,相当于变相调⽤了函数, 但这东西不会在函数结束后跳转设定的uc_link
int setcontext(const ucontext_t *ucp);
// 修改由getcontext获取到的上下⽂指针ucp,将其与⼀个函数func进⾏绑定,⽀持指定func运⾏时的参数,
// 在调⽤makecontext之前,必须⼿动给ucp分配⼀段内存空间,存储在ucp->uc_stack中,这段内存空间将作为func函数运⾏时的栈空间,
// 同时也可以指定ucp->uc_link,表示函数运⾏结束后恢复uc_link指向的上下⽂,
// 如果不赋值uc_link,那func函数结束时必须调⽤setcontext或swapcontext以重新指定⼀个有效的上下⽂,否则程序就跑⻜了
// makecontext执⾏完后,ucp就与函数func绑定了,调⽤setcontext或swapcontext激活ucp时,func就会被运⾏
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
// 恢复ucp指向的上下⽂,同时将当前的上下⽂存储到oucp中,
// swapcontext也不会返回,⽽是会跳转到ucp上下⽂对应的函数中执⾏,相当于调⽤了函数, 在函数返回后, 回自动跳转设定的uc_link的上下文
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);

一个简单的函数切换

    我们可以使用ucontext库变相调用函数

#include <iostream>
#include <ucontext.h>
#include <cstdlib>
using namespace std;ucontext_t _main;
ucontext_t f;void func() {cout << "func" << endl;
}
void FiberCreate(ucontext_t* f, void (*func)(), ucontext_t* next) {getcontext(f);void* s = malloc(1024 * 1024);  // 分配堆栈f->uc_stack.ss_sp = s;f->uc_stack.ss_size = 1024 * 1024;f->uc_link = next;  // 设置返回上下文为 nextmakecontext(f, func, 0);  // 设定函数
}
int main() {// 初始化 main 上下文getcontext(&_main);//设定返回main函数的上下文FiberCreate(&f, func, &_main);// 切换到 fswapcontext(&_main, &f);cout << "Main" << endl;free(f.uc_stack.ss_sp);return 0;
}

在这里插入图片描述

自动调用

    根据swapcontext会使得函数自动跳转设定的上下文, 我们可以设定一定的调用逻辑, 小心程序跑飞

#include <iostream>
#include <ucontext.h>
#include <cstdlib>
using namespace std;ucontext_t _main;
ucontext_t f1, f2, f3, f4;void func001() {cout << "func001" << endl;
}
void func002() {cout << "func002" << endl;
}
void func003() {cout << "func003" << endl;
}
void func004() {cout << "func004" << endl;
}
void FiberCreate(ucontext_t* f, void (*func)(), ucontext_t* next) {getcontext(f);void* s = malloc(1024 * 1024);  // 分配堆栈f->uc_stack.ss_sp = s;f->uc_stack.ss_size = 1024 * 1024;f->uc_link = next;  // 设置返回上下文为 nextmakecontext(f, func, 0);  // 设定函数
}int main() {// 初始化 main 上下文getcontext(&_main);FiberCreate(&f1, func001, &f2);FiberCreate(&f2, func002, &f3);FiberCreate(&f3, func003, &f4);FiberCreate(&f4, func004, &_main);  // f4 结束后返回 main// 切换到 f1swapcontext(&_main, &f1);cout << "Main" << endl;free(f1.uc_stack.ss_sp);free(f2.uc_stack.ss_sp);free(f3.uc_stack.ss_sp);free(f4.uc_stack.ss_sp);return 0;
}

设定uc_link后就能来来回回切换
在这里插入图片描述

协程类的实现

    掌握上述基础后我们就可以实现一个简单的协程类了
具体实现介绍

  • 无栈协程: 实现的是无栈协程, 协程的上下文保存在堆上
  • 非对称协程: 实现的是非对称协程, 协程间不能自由切换, 必须和主协程交替切换
  • 使用智能指针管理: 每一个协程对象都只能从堆上new, 继承std::enable_shared_from_this方便获得this的智能指针对象
    完整代码

接口

class Fiber : public std::enable_shared_from_this<Fiber>
{
public:using func_t=std::function<void()>;using ptr=std::shared_ptr<Fiber>;enum class state{INIT,   //初始化HOLD,   //挂起EXEC,   //运行TERM,   //结束READY,  //就绪};
public:Fiber(func_t cb,bool isrunInSchedule=true,uint32_t stack_size=128*1024);~Fiber();//重置协程函数,重置状态INIT/TERM,复用栈空间void reset(func_t cb);//切换到当前协程运行void swapIn();//切换到后台运行void swapOut();//调用协程void call();//获取协程状态state getState(){ return m_s;}
public://设置一个协程为当前协程static void SetThis(Fiber*f);//获取当前执行协程的智能指针static Fiber::ptr Get_this();//当前运行协程切换到后台, 并设置为Ready状态static void YeildtoReady();//当前运行协程切换到后台, 并设置为Hold状态static void YeildtoHold();//获取总协程数static uint64_t GetFiberNum();//协程执行的方法, 会在这里面执行回调函数static void MainFunc();
private://用于创建主协程Fiber();
private:uint64_t m_id=0;            //协程iduint32_t m_stack_size=0;    //协程栈空间大小ucontext_t m_ctx;           //协程上下文void* m_stack=nullptr;      //协程使用的栈state m_s=state::INIT;      //协程状态func_t m_cb;                //协程具体回调bool m_runInSchedule;       //是否参与调度器调度
};

全局变量

//协程id
std::atomic<uint64_t> s_fiber_id(0);
//协程数量
std::atomic<uint64_t> s_fiber_num(0);

线程局部变量

    每个线程记录自己的主协程和正在调度的协程, 方便切换

//线程当前执行的协程
thread_local Fiber* t_fiber=nullptr;
//线程的主协程
thread_local Fiber::ptr t_thread_fiber=nullptr;//using Fiber::ptr=std::shared_ptr<Fiber>;

malloc封装

    方便后面扩展内存申请方式或者使用内存池

class MallocStackAllocator
{
public:
static void* Alloc(size_t size) {return malloc(size);
}
static void Dealloc(void* vp, size_t size) {return free(vp);
}
};

协程切换

//切换到该协程执行
void Fiber::swapIn()
{SetThis(this);swapcontext(&t_thread_fiber->m_ctx,&m_ctx);
}   
//该协程切换到后台运行
void Fiber::swapOut()
{   SetThis(t_thread_fiber.get());swapcontext(&m_ctx,&t_thread_fiber->m_ctx);
}       
//手动调用协程, 需要提前自己先切换到主协程
void Fiber::call()
{m_s=state::EXEC;swapcontext(&t_thread_fiber->m_ctx,&m_ctx);
}
//切换回主协程并设置READY状态
void Fiber::YeildtoReady()
{Fiber::ptr p=Get_this();p->m_s=state::READY;p->swapOut();
}
//切换回主协程并设置HOLD状态
void Fiber::YeildtoHold()
{Fiber::ptr p=Get_this();p->m_s=state::HOLD;p->swapOut();
}

构造函数

Fiber::Fiber(func_t cb,bool isrunInSchedule,uint32_t stack_size):m_id(s_fiber_id++),m_stack_size(stack_size),m_cb(cb),m_runInSchedule(isrunInSchedule)                              
{   //协程数量增加++s_fiber_num;//分配协程栈空间m_stack=StackAllocator::Alloc(m_stack_size);//初始化上下文getcontext(&m_ctx);m_ctx.uc_link=nullptr;m_ctx.uc_stack.ss_sp=m_stack;m_ctx.uc_stack.ss_size=m_stack_size;makecontext(&m_ctx,&Fiber::MainFunc,0);LOG_ROOT_INFO("create Fiber id:%d",m_id);//日志
}
//获取当前协程的智能指针
Fiber::ptr Fiber::Get_this()
{if(t_fiber!=nullptr){//返回当前协程的指针指针对象return t_fiber->shared_from_this();}//如果当前线程没有协程, 就创建一个主协程Fiber::ptr main_fiber(new Fiber);t_thread_fiber=main_fiber;return main_fiber->shared_from_this();
}
//私有构造, 只用于创建主协程, 主协程不需要申请堆空间
Fiber::Fiber()
{SetThis(this);getcontext(&m_ctx);m_s=state::EXEC;m_id=s_fiber_id;++s_fiber_num;++s_fiber_id;LOG_ROOT_INFO("create main Fiber id:%d",m_id);
}

协程执行的方法

void Fiber::MainFunc()
{//获取当前协程的智能指针Fiber::ptr p=Get_this();//执行回调, 执行完后清理p->m_cb();p->m_cb=nullptr;p->m_s=state::TERM;auto cur=p.get();//之前获取了当前协程的智能指针, 现在手动让计数器--p.reset();//切换回主协程cur->swapOut();
}

测试

    当然我们的协程总不能自己手动调来调去, 我们还需要协程调度器(关注后面的文章)

协程切换

    看看能不能跑, 先简单使用一下

#include<iostream>
#include"Fiber.hpp"
using namespace MindbniM;
void func1()
{std::cout<<"func1"<<std::endl;
}
void func2()
{std::cout<<"func2"<<std::endl;
}
void func3()
{std::cout<<"func3"<<std::endl;                                                                                                      
}                         
int main()                
{                         //首先创建主协程      Fiber::Get_this();    Fiber::ptr f1=std::make_shared<Fiber>(func1);Fiber::ptr f2=std::make_shared<Fiber>(func2);Fiber::ptr f3=std::make_shared<Fiber>(func3);//切换到f1f1->swapIn();//切换到f2f2->swapIn();//切换到f3f3->swapIn();return 0;
}

三个函数顺利执行
在这里插入图片描述

手动切换+复用

    协程一个优势就能可以复用空间

#include<iostream>
#include"Fiber.hpp"
using namespace MindbniM;
void func1()
{std::cout<<"func1"<<std::endl;
}
void func2()
{std::cout<<"func2"<<std::endl;
}
void func3()
{std::cout<<"func3"<<std::endl;
}
int main()
{//首先创建主协程Fiber::Get_this();Fiber::ptr f1=std::make_shared<Fiber>(func1);//试试手动切换//切换到f1Fiber::SetThis(f1.get());//执行f1f1->call();//切换回主协程f1->swapOut();//复用f1的空间f1->reset(func2);f1->swapIn();f1->reset(func3);f1->swapIn();return 0;
}

结果一样

在这里插入图片描述

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

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

相关文章

【从0开始搭建微服务并进行部署】SpringBoot+dubbo+zookeeper

文章目录 说明环境搭建创建项目父模块设置子模块 dubbo-api子模块 dubbo-provider子模块 dubbo-consumer测试项目 docker部署项目完整项目地址 说明 jdk1.8SpringBoot2.x低版本dubbo&#xff1a;请查看之前教程【微服务】SpringBootDubboZooKeeper 实战 关于本教程将采用jdk1…

HTML流光爱心

文章目录 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心&#xff08;双心版&#xff09;1…

如何用AI绘画工具生成中国风插画?Midjourney保持风格一致出图

​ 如何运用AI绘画工具如Midjourney&#xff0c;生成符合我们特定要求的艺术作品是一门精进的技巧&#xff0c;尤其当你想生成具有鲜明特色的国风插画时&#xff0c;纯文本提示词的局限性常常使我们难以达到预期效果。然而&#xff0c;借助Midjourney的高级参数功能——特别是s…

【课程学习】随机过程之泊松过程

随机过程之泊松过程 泊松分布泊松过程 泊松分布 二项分布是离散性的分布&#xff0c;泊松分布是把二项分布取n趋于无穷得到的连续分布。也就是在一段时间内不停的观察某件事情发生的次数。 如&#xff1a;一个小时内观察一段路上经过行人的数目&#xff0c;如果每个半个小时观…

Prompt 模版解析:诗人角色的创意引导与实践

Prompt 模版解析&#xff1a;诗人角色的创意引导与实践 Prompt 模版作为一种结构化工具&#xff0c;旨在为特定角色——本例中的“诗人”——提供明确的指导和框架。这一模版详尽地描绘了诗人的职责、擅长的诗歌形式以及创作规则&#xff0c;使其能在自动化系统中更加精确地执…

【Unity】双摄像机叠加渲染

一、前言 之前我在做我的一个Unity项目的时候&#xff0c;需要绘制场景网格的功能&#xff0c;于是就用到了UnityEngine.GL这个图形库来绘制&#xff0c;然后我发现绘制的网格线是渲染在UI之后的&#xff0c;也就是说绘制出来的图形会遮盖在UI上面&#xff0c;也就导致一旦这些…

计算机网络:物理层 —— 物理层下的传输媒体

文章目录 传输媒体导向性媒体同轴电缆双绞线光纤光纤分类中心波长光纤规格光纤的优缺点 非导向性媒体ISM 频段无线电波微波激光红外线可见光 传输媒体 传输媒体是计算机网络设备之间的物理通路&#xff0c;也称为传输介质或传输媒介&#xff0c;并不包含在计算机网络体系结构中…

什么是 ARP 欺骗和缓存中毒攻击?

如果您熟悉蒙面歌王&#xff0c;您就会明白蒙面歌王的概念&#xff1a;有人伪装成别人。然后&#xff0c;当面具掉下来时&#xff0c;您会大吃一惊&#xff0c;知道了这位名人是谁。类似的事情也发生在 ARP 欺骗攻击中&#xff0c;只是令人惊讶的是&#xff0c;威胁行为者利用他…

ModuleNotFoundError: No module named ‘package‘

报错&#xff1a; Traceback (most recent call last): File “”, line 198, in run_module_as_main File “”, line 88, in run_code File "D:\python\helloworld.venv\Scripts\pip.exe_main.py", line 4, in File "D:\python\helloworld.venv\Lib\site-pac…

android 全面屏最底部栏沉浸式

Activity的onCreate方法中添加 this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); Android 系统 Bar 沉浸式完美兼容方案自 Android 5.0 版本&#xff0c;Android 带来了沉浸式系统 ba - 掘金 (juejin.cn)https://juejin.cn/post/7075578…

AI类课程的笔记

信息论、导论、模式识别&#xff08;数据挖掘&#xff09;、语义网络与知识图谱、深度学习、强化学习 &#xff08;零&#xff09;信息论 详见另一篇博文 信息论自总结笔记(仍然在更新)_信息论也更新了-CSDN博客https://blog.csdn.net/sinat_27382047/article/details/12690…

Python+Django微信小程序前后端人脸识别登录注册

程序示例精选 PythonDjango微信小程序前后端人脸识别登录注册 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjango微信小程序前后端人脸识别登录注册》编写代码&#xff0c;代码整…

并查集——从LeetCode题海中总结常见套路

目录 并查集定义 LeetCode128.最长连续序列 先去重再sort&#xff1a; 改进去重的方法&#xff1a; 参考&#xff1a; 并查集定义 在计算机科学中&#xff0c;并查集是一种树型的数据结构&#xff0c;用于处理一些不交集&#xff08;Disjoint Sets&#xff09;的合并及查…

重学SpringBoot3-集成Redis(四)之Redisson

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;四&#xff09;之Redisson 1. 添加 Redisson 依赖2. 配置 Redisson 客户端3. 使用 Redisson 实现分布式锁4. 调用分布式锁5. 为什…

会声会影导出视频mp4格式哪个最高清,会声会影输出格式哪个清晰

调高分辨率后&#xff0c;mp4视频还是不清晰。哪怕全部使用4K级素材&#xff0c;仍然剪不出理想中的高画质作品。不是你的操作有问题&#xff0c;而是剪辑软件没选对。Corel公司拥有全球顶尖的图像处理技术&#xff0c;该公司研发的会声会影视频剪辑软件&#xff0c;在过去的20…

Linux性能调优技巧

目录 前言1. CPU性能优化1.1 调整CPU调度策略1.2 合理分配多核处理 2. 内存性能优化2.1 调整内存分配策略2.2 缓存和分页优化 3. 磁盘I/O性能优化3.1 使用合适的I/O调度器3.2 磁盘分区和文件系统优化 4. 网络性能优化4.1 优化网络参数4.2 调整网络拥塞控制算法 5. 系统监控与优…

20款奔驰CLS300升级原厂抬头显示HUD 23P智能辅助驾驶 触摸屏人机交互系统

以下是为您生成的一份关于 18 款奔驰 CLS 老款改新款的改装文案&#xff1a; 18 款奔驰 CLS 老款改新款&#xff1a;科技升级&#xff0c;畅享极致驾驶体验 在汽车改装的世界里&#xff0c;每一次的升级都是对卓越的追求。今天&#xff0c;让我们一同探索 18 款奔驰 CLS 老款改…

记一次教学版内网渗透流程

信息收集 如果觉得文章写的不错可以共同交流 http://aertyxqdp1.target.yijinglab.com/dirsearch dirsearch -u "http://aertyxqdp1.target.yijinglab.com/"发现 http://aertyxqdp1.target.yijinglab.com/joomla/http://aertyxqdp1.target.yijinglab.com/phpMyA…

Windows Ubuntu下搭建深度学习Pytorch训练框架与转换环境TensorRT

Windows Ubuntu下搭建深度学习Pytorch训练框架与转换环境TensorRT JetBrains2024&#xff08;IntelliJ IDEA、PhpStorm、RubyMine、Rider……&#xff09;安装包Anaconda Miniconda安装.condarc 文件配置镜像源查看conda的配置和源(channel)自定义conda虚拟环境路径conda常用命…

el-pagination组件封装

组件使用 源代码&#xff1a; <script setup> import Pagination from /components/pagination/index.vue import {ref} from "vue";const pageNum ref(1) const pageSize ref(10) const total ref(120)function loadData() {// 加载数据 } </script>…