协程库项目—协程类模块

news2024/11/17 4:49:54

ucontext_t结构体、非对称协程

协程类

ucontext_t结构体

头文件中定义的四个函数(getcontext(), setcontext(), makecontext(), swapcontext())和两个结构类型(mcontext_t, ucontext_t)在一个进程中实现用户级的线程切换。
其中,mcontext_t类型与机器相关,不透明;ucontext_t结构体至少包含以下几个域:

typedef struct ucontext {
    struct ucontext *uc_link;
    sigset_t         uc_sigmask;
    stack_t          uc_stack;
    mcontext_t       uc_mcontext;
    ...
} ucontext_t;

当当前上下文运行终止时,系统会恢复uc_link指向的上下文;uc_sigmask为该上下文中的阻塞信号集合;uc_stack为该上下文中使用的栈;uc_mcontext保存的上下文的特定机器表示,包括调用线程的特定寄存器等。

下面是这四个函数的详细介绍:

int getcontext(ucontext_t *ucp);

初始化ucp结构体,将当前的上下文保存到ucp中。

int setcontext(const ucontext_t *ucp);

设置当前的上下文为ucp,setcontext的上下文ucp应该通过getcontext或者makecontext取得。如果调用成功则不返回。如果上下文是通过调用getcontext()取得,程序会继续执行这个调用(从该上下文的状态开始继续执行,即调用getcontext处后接着执行)。如果上下文是通过调用makecontext取得,程序会调用makecontext函数的第二个参数指向的函数,如果func函数返回,则恢复makecontext第一个参数指向的上下文。如果uc_link为NULL,则线程退出。

void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link。当上下文通过setcontext或者swapcontext激活后,执行func函数,argc为func的参数个数,后面是func的参数序列。当func执行返回后,继承的上下文被激活,如果继承上下文为NULL时,线程退出。

int swapcontext(ucontext_t *oucp, ucontext_t *ucp);

保存当前上下文到oucp结构体中,然后激活upc上下文。如果执行成功,getcontext返回0,setcontext和swapcontext不返回;如果执行失败,getcontext,setcontext,swapcontext返回-1,并设置对应的errno。

小结一下:
makecontext:初始化一个ucontext_t,func参数指明了该context的入口函数,argc为入口参数的个数,每个参数的类型必须是int类型。另外在makecontext前,一般需要显示的初始化栈信息以及信号掩码集同时也需要初始化uc_link,以便程序退出上下文后继续执行。
swapcontext:原子操作,该函数的工作是保存当前上下文并将上下文切换到新的上下文运行。
getcontext:将当前的执行上下文保存在cpu中,以便后续恢复上下文
setcontext:将当前程序切换到新的context,在执行正确的情况下该函数直接切换到新的执行状态,不会返回。

⾮对称模型

非对称协程(asymmetric
coroutines)是跟一个特定的调用者绑定的,协程让出CPU时,只能让回给调用者。何为非对称呢?在于协程的调用(call/resume)和返回(return/yield)的地位不对等。程序控制流转移到被调用者协程,而被调用者只能返回最初调用它的协程。
对称协程(symmetric
coroutines)在于协程调用和返回的地位是对等的。启动之后就跟启动之前的协程没有任何关系了。协程的切换操作,一般而言只有一个操作,yield,用于将程序控制流转移给其他的协程。对称协程机制一般需要一个调度器的支持,按一定调度算法去选择yield的目标协程。

这里采用的是非对称模型,
保证⼦协程不能再创建新的协程,即协程不能嵌套调⽤,⼦协程只能与主线程进行切换。注意下图中子协程只能切换回主协程,不能创建新的子协程。
在这里插入图片描述

对于非对称协程,⼦协程和⼦协程切换导致线程主协程跑⻜的关键原因在于,每个线程只有两个线程局部变量⽤于保存当前的协程上下⽂信息。也就是说线程任何时候都最多只能知道两个协程的上下⽂,其中⼀个是当前正在运⾏协程的上下⽂,另⼀个是线程主协程的上下⽂,如果⼦协程和⼦协程切换,那这两个上下⽂都会变成⼦协程的上下⽂,线程主 协程的上下⽂丢失了,程序也就跑⻜了。

简化协程状态

只设置三种协程状态:就绪态、运⾏态和结束态,⼀个协程要么正在运⾏(RUNNING),要么准备运⾏(READY),要运⾏
结束(TERM)。
在这里插入图片描述

协程操作

协程创建操作
创建线程主协程:只需要将协程设置为当前运行协程,协程转为RUNING,获取当前上下文。
创建⽤户协程:则需要额外创建栈空间和绑定协程入口函数

/**
* @brief 线程主协程构造函数
* @attention ⽆参构造函数只⽤于创建线程的第⼀个协程,也就是线程主函数对应的协程,
* 这个协程只能由GetThis()⽅法调⽤,所以定义成私有⽅法
*/
Fiber::Fiber(){
 	SetThis(this);
 	m_state = RUNNING;
 	if (getcontext(&m_ctx)) {
 		Fzk_ASSERT2(false, "getcontext");
 	}
 	++s_fiber_count;
 	m_id = s_fiber_id++; // 协程id从0开始,⽤完加1
 	Fzk_LOG_DEBUG(g_logger) << "Fiber::Fiber() main id = " << m_id;
}

/**
* @brief 构造函数,⽤于创建⽤户协程
* @param[] cb 协程⼊⼝函数
* @param[] stacksize 栈⼤⼩
*/
Fiber::Fiber(std::function<void()> cb, size_t stacksize)
 : m_id(s_fiber_id++)
 , m_cb(cb) {
	 ++s_fiber_count;
 	m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();
 	m_stack = StackAllocator::Alloc(m_stacksize);
 	if (getcontext(&m_ctx)) {
 		Fzk_ASSERT2(false, "getcontext");
 	}
 	m_ctx.uc_link = nullptr;
 	m_ctx.uc_stack.ss_sp = m_stack;
 	m_ctx.uc_stack.ss_size = m_stacksize;
 	makecontext(&m_ctx, &Fiber::MainFunc, 0);
 	Fzk_LOG_DEBUG(g_logger) << "Fiber::Fiber() id = " << m_id;
}

协程间执行权转换操作

resume:将协程(非TERM、RUNNING状态)设置为当前运行协程,协程转为RUNING,恢复协程运行
yield:将主协程设置为当前运行协程,协程转为READY,让出执⾏权
主要区别:前者将保存线程主协程上下文,并切换运行子协程的上下文;前者将保存子协程上下文,并切换运行主协程的上下文;

/// @brief 恢复协程运行
///恢复该协程的运行。
void Fiber::resume() {
    Fzk_ASSERT(m_state != TERM && m_state != RUNNING);
    SetThis(this);
    m_state = RUNNING;
    //涉及后面的协程调度,如果协程参与调度器调度,那么应该和调度器的主协程进行swap,而不是线程主协程
    // if (m_runInScheduler) {
    //     if (swapcontext(&(Scheduler::GetMainFiber()->m_ctx), &m_ctx)) {
    //         Fzk_ASSERT2(false, "swapcontext");
    //     }
    // } else {
    //      if (swapcontext(&(t_thread_fiber->m_ctx), &m_ctx)) {
    //          Fzk_ASSERT2(false, "swapcontext");
    //      }
    //  }
    //发生段错误,已解决,是新创建的子协程未保存其上下文,导致&m_ctx对未初始化对象进行取地址操作
    if (swapcontext(&(t_thread_fiber->m_ctx), &m_ctx)) {
              Fzk_ASSERT2(false, "swapcontext");
    }
}
/// @brief 当前协程让出执⾏权
///当前协程让出执⾏权, 当前协程的状态有两种情况:1、协程函数未执行完,更新为READY; 2、执行完更新为TERM
void Fiber::yield() {
    /// 协程运行完之后会自动yield一次,用于回到主协程,此时状态已为结束状态
    Fzk_ASSERT(m_state == RUNNING || m_state == TERM);
    SetThis(t_thread_fiber.get());
    if(m_state != TERM) {
        m_state = READY;
    }
    // 如果协程参与调度器调度,那么应该和调度器的主协程进行swap,而不是线程主协程
    // if(m_runInScheduler) {
    //     if(swapcontext(&m_ctx, &(Scheduler::GetMainFiber()->m_ctx))) {
    //         Fzk_ASSERT2(false, "swapcontext");
    //     }
    // } else {
    //      if (swapcontext(&m_ctx, &(t_thread_fiber->m_ctx))) {
    //          Fzk_ASSERT2(false, "swapcontext");
    //      }
    //  }
    if (swapcontext(&m_ctx, &(t_thread_fiber->m_ctx))) {
            Fzk_ASSERT2(false, "swapcontext");
    }
}

后续工作:实现协程调度类

为使得协程类能够通过调度器来运⾏,需要对已实现的协程类进行以下具体操作:

  1. 给协程类增加⼀个bool类型的成员m_runInScheduler,⽤于记录该协程是否通过调度器来运⾏。
  2. 创建协程时,根据协程的身份指定对应的协程类型,具体来说,只有想让调度器调度的协程的 m_runInScheduler值为true,线程主协程和线程调度协程的m_runInScheduler都为false。
  3. resume⼀个协程时,如果如果这个协程的m_runInScheduler值为true,表示这个协程参与调度器调度,那它应该和三个线程局部变量中的调度协程上下⽂进⾏切换,同理,在协程yield时,也应该恢复调度协程的上下⽂,表示从⼦协程切换回调度协程
  4. 如果协程的m_runInScheduler值为false,表示这个协程不参与调度器调度,那么在resume协程时,直接和线程主协程切换就可以了,yield也⼀样,应该恢复线程主协程的上下⽂。m_runInScheduler值为false的协程。上下⽂切换完全和调度协程⽆关,可以脱离调度器使⽤。

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

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

相关文章

MySQL进阶之(五)InnoDB数据存储结构之表空间

五、InnoDB数据存储结构之表空间 5.1 数据页加载的三种方式5.1.1 内存读取5.1.2 随机读取5.1.3 顺序读取 5.2 区5.2.1 为什么要有区&#xff1f;5.2.2 碎片区5.2.3 区的分类 5.3 段5.4 表空间5.4.1 独立表空间5.4.2 系统表空间 在数据页结构中提到过&#xff0c;页的上层结构中…

RN开发搬砖经验之-如何处理FlashList组件加载后调用scrollToIndex没有滚动指定位置

前言 如题&#xff0c;这里只能说是处理&#xff0c;起正向作用的临时方案&#xff0c;因为我也着实没搞懂这个BUG的具体原因&#xff0c;看github上有提相关的issuesFor long lists with different item types scrollToIndex does not work reliable&#xff0c;但看官方没有…

PLC的FC与FB模块程序的功能解析

前文讲了在西门子系列的PLC中四个程序模块的描述&#xff0c;从S7-1200PLC开始就有FC和FB程序块了&#xff0c;但在使用的时候&#xff0c;一些使用者还是不好理解&#xff0c;以至于不知道该如何选择。今天&#xff0c;我们就用大白话的方式给大家讲解FC与FB的功能。 1、FC与…

前端学习之列表标签

目录 有序列表 结果 无序标签 结果 数据标签 结果 有序列表 &#xff08;注&#xff1a;注释是解释&#xff09; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Document</title> </…

Image Demoireing with Learnable Bandpass Filters

一、简介 标题:Image Demoireing with Learnable Bandpass Filters(https://openaccess.thecvf.com/content_CVPR_2020/papers/Zheng_Image_Demoireing_with_Learnable_Bandpass_Filters_CVPR_2020_paper.pdf) 期刊:CVPR 时间:2020 作者:Bolun Zheng, Shanxin Yuan, …

Google浏览器122.下载驱动

打开网址&#xff1a;Chrome for Testing availability (googlechromelabs.github.io) 选择stable 选择对应的驱动就行

前后端交互理解 简易表白墙(servlet)

前后端交互理解 简易表白墙&#xff08;servlet&#xff09; 文章目录 前后端交互理解 简易表白墙&#xff08;servlet&#xff09;后端核心内容前后端交互接口约定后端代码展示 上期介绍过 Servlet API &#xff0c;本篇文章目的是借助 servlet 做出一个完整的网站。在一个网站…

Spring Boot 面试题及答案整理,最新面试题

Spring Boot中的自动配置是如何工作的&#xff1f; Spring Boot的自动配置是其核心特性之一&#xff0c;它通过以下方式工作&#xff1a; 1、EnableAutoConfiguration注解&#xff1a; 这个注解告诉Spring Boot开始查找添加了Configuration注解的类&#xff0c;并自动配置它们…

怎么看待Groq

用眼睛看。 就是字面上的意思用眼睛看。 我属于第一波玩到的,先给大家一个直观的印象,Groq到底有多快。 目前Groq只能选Llama的70b,和Mixtral的MoE,那我选7*8的这个MoE模型来实验。 这么好些字大概花了不到1秒,流式响应,其实是不是流式已经没那么重要了 ,然后看每秒Toke…

第二十二周周报

论文研读&#xff1a;Camera Distance-aware Top-down Approach for 3D Multi-person Pose Estimation from a Single RGB Image 粗读10篇文献。 通过图2 我可以知道这个论文大概实现的这个姿态估计效果的方法&#xff0c;首先是把图片输入到DetectNet网络&#xff0c;该网络…

也就老炮知道:UI设计师和前端,以前叫美工和切片。

过去&#xff0c;UI设计师常常被称为美工&#xff0c;而前端开发人员被称为切片工。这是因为在过去的网页设计中&#xff0c;UI设计师主要负责设计网页的外观和用户界面&#xff0c;包括颜色、布局、图标等&#xff0c;类似于传统的美工工作。而前端开发人员则负责将UI设计师提…

搭建nacos集群,并通过nginx实现负载均衡

nacos、eureka、consul、zookeeper等都是常用的微服务注册中心&#xff0c;这篇文章详细介绍一下在Ubuntu操作系统上搭建一个nacos的集群&#xff0c;以及通过nginx的反向代理功能实现nacos的负载均衡。 目录 一、安装nacos 1、安装nacos 2、修改nacos配置文件 3、创建naco…

Cocos Creator 2d光照

godot游戏引擎是有2d光照的&#xff0c;用起来感觉还是很强大的&#xff0c;不知道他是怎么搞的&#xff0c;有时间看看他们怎么实现的。 之前一直以为cocos社区里面没有2d光照的实现&#xff0c;偶然看到2d实现的具体逻辑&#xff0c;现在整理如下&#xff0c; 一&#xff1…

Spring Boot 中解决跨域的多种方式

Spring Boot 中解决跨域的多种方式 《踏过跨域障碍&#xff1a;Spring Boot 中解决跨域的多种方式》摘要引言正文何为跨域跨域问题出现特征方式一&#xff1a;使用 CrossOrigin 注解方式二&#xff1a;自定义 WebMvcConfigurer方式三&#xff1a;使用 Filter 进行跨域配置 结论…

java之lombok

Lombok是一个实用的java类库&#xff0c;能通过注解的形式自动生成构造器&#xff0c; getter setter equsls hashcode tostring等方法 并且可以自动化生成日志变量&#xff0c;简化java开发&#xff0c;提高效率 作用 导入 <dependency><groupId>org.projectlomb…

集合框架(一)Set系列集合

Set<E>是一个接口 特点 无序&#xff1a;添加数据的顺序和获取出的数据顺序不一致&#xff1b;不重复&#xff0c;无索引 注意&#xff1a;Set要用到的常用方法&#xff0c;基本上就是collection提供的!自己几乎没有额外新增一些常用功能! HashSet集合的底层原理 前置知…

我们的一生都是在挤火车。

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 昨天从燕郊坐火车回石家庄&#xff0c;由于赶上元旦假期&#xff0c;所有高铁票都售罄&#xff0c;一张普通火车票&#xff0c;还是一周前就买才买到的。 从燕郊站&#xff0c;到北京站&#xff0c;然后地铁去北京西站…

C++虚继承的一些细节

C虚继承的一些细节 何时使用虚继承普通继承的类对象布局虚继承类对象布局虚函数表指针虚函数表内容 何时使用虚继承 看代码&#xff0c;代码主要是菱形继承&#xff0c;base里面的成员变量会存在二义性 #include<iostream> using namespace std;class base { public:in…

【机器学习】从线性回归模型看一个简单的成本函数

&#x1f338;博主主页&#xff1a;釉色清风&#x1f338;文章专栏&#xff1a;机器学习&#x1f338;今日语录&#xff1a;事情不做&#xff0c;越想越难&#xff1b;事情做了&#xff0c;越做越容易。 从线性回归模型看一个简单的成本函数 &#x1f33c;引入&#xff1a;模型…

基于SpringBoo的火车订票管理系统(程序+文档+数据库)

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一、研究背景…