协程库项目—协程类模块

2024-03-11 05:12

本文主要是介绍协程库项目—协程类模块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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开始,⽤完加1Fzk_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.chinasem.cn/article/796749

相关文章

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

Python 中 requests 与 aiohttp 在实际项目中的选择策略详解

《Python中requests与aiohttp在实际项目中的选择策略详解》本文主要介绍了Python爬虫开发中常用的两个库requests和aiohttp的使用方法及其区别,通过实际项目案... 目录一、requests 库二、aiohttp 库三、requests 和 aiohttp 的比较四、requ

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

Python项目打包部署到服务器的实现

《Python项目打包部署到服务器的实现》本文主要介绍了PyCharm和Ubuntu服务器部署Python项目,包括打包、上传、安装和设置自启动服务的步骤,具有一定的参考价值,感兴趣的可以了解一下... 目录一、准备工作二、项目打包三、部署到服务器四、设置服务自启动一、准备工作开发环境:本文以PyChar

多模块的springboot项目发布指定模块的脚本方式

《多模块的springboot项目发布指定模块的脚本方式》该文章主要介绍了如何在多模块的SpringBoot项目中发布指定模块的脚本,作者原先的脚本会清理并编译所有模块,导致发布时间过长,通过简化脚本... 目录多模块的springboot项目发布指定模块的脚本1、不计成本地全部发布2、指定模块发布总结多模

SpringBoot项目删除Bean或者不加载Bean的问题解决

《SpringBoot项目删除Bean或者不加载Bean的问题解决》文章介绍了在SpringBoot项目中如何使用@ComponentScan注解和自定义过滤器实现不加载某些Bean的方法,本文通过实... 使用@ComponentScan注解中的@ComponentScan.Filter标记不加载。@C

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链