C++协程项目之协程库学习与实践(协程函数学习、线程切换实践)

news2024/12/24 7:24:17

网上协程实现原理大概有这么几种:调库、汇编、原语级别(可能会破坏原本语义)。我们今天简单学习和实践的是一种利用linux下库函数实现的协程。

首先来看这样一段代码:

#include <iostream>
#include <ucontext.h>
#include <unistd.h>

int main(int argc, char** argv) {
    ucontext_t context;
    getcontext(&context);
    
    std::cout << "hello world" << std::endl;
    sleep(1);
    
    setcontext(&context);
    return 0;
}

编译运行指令如下:

g++ new.cpp -o new

./new

注意这个库只有在linux下才支持,可以用虚拟机进行尝试。

头文件unistd.h是unix std标准命名空间的缩写,我们用到了sleep休眠函数,所以要包含这个头文件。

运行效果如图,如果不用Ctrl+C或者Ctrl+Z终止进程的话,就会一直运行。这是怎么回事呢?

其实是我们用getcontext函数保存了context地址处的上下文信息,具体就是这个结构体保存的信息,这个解释起来有些复杂,最简单的解释就是这个结构体包含一个该结构体指针类型的变量,和众多的上下文信息,包括上下文中的阻塞信号,使用的内存栈的地址、寄存器名字等等。我们这里用到的是恢复到原先设置好的栈位置上去,让其达到了循环运行的效果。

这也就是协程的奇妙之处了。

ucontext.h库中还包含了这样几个函数:

int getcontext(ucontext_t *uc);

int setcontext(ucontext_t *uc);

void makecontext(ucontext_t *uc, void (*func)(), int argc, char** argv);

int swapcontext(ucontext_t *tuc, ucontext_h *nuc); 

其中的参数名我按照自己的风格改写了下,第四个函数第一个参数是保存地址,第二个参数是唤醒的协程的上下文地址。

第三个函数是将指定上下文中栈地址处的值设置为该函数和其参数,方便直接执行该函数。

第一第二个上面已经使用过了。就不看了。

需要注意的是三个有返回值的函数在失败时返回-1。成功时getcontext返回0,其他两个不返回。

下面我们尝试用这几个函数实现两个线程之间的切换。

需求如下:

#include <iostream>
#include <ucontext.h>

ucontext_t main_context;
ucontext_t thread_context;

void thread1() {
    int n = 3;
    while (n--)
        std::cout << "I'm thread1" << std::endl;
    setcontext(&main_context);
}

void thread2() {
    int n = 4;
    while (n--)
        std::cout << "I'm thread2" << std::endl;
    setcontext(&main_context);
}

void thread3() {
    int n = 5;
    while (1)
        std::cout << "I'm threa3" << std::endl;
    setcontext(&main_context);
}

int main(int argc, char** argv) {
    makecontext(&thread_context, &thread1, 0);
    while (swapcontext(&main_context, &thread_context) == -1);
    makecontext(&thread_context, &thread2, 0);
    while (swapcontext(&main_context, &thread_context) == -1);
    makecontext(&thread_context, &thread3, 0);
    while (swapcontext(&main_context, &thread_context) == -1);
    return 0;
}

效果如下:

简化后代码如下:

#include <iostream>
#include <ucontext.h>

void thread1() {
    int n = 3;
    while (n--)
        std::cout << "I'm thread1" << std::endl;
}

void thread2() {
    int n = 4;
    while (n--)
        std::cout << "I'm thread2" << std::endl;
}

void thread3() {
    int n = 5;
    while (n--)
        std::cout << "I'm threa3" << std::endl;
}

int main(int argc, char** argv) {
    ucontext_t main_context;
    ucontext_t thread_context;
    char stack[1024*128];
    getcontext(&thread_context);
    thread_context.uc_stack.ss_sp = stack;
    thread_context.uc_stack.ss_size = sizeof(stack);
    thread_context.uc_stack.ss_flags = 0;
    thread_context.uc_link = &main_context;
    makecontext(&thread_context, (void (*)())thread1, 0);
    swapcontext(&main_context, &thread_context);
    makecontext(&thread_context, (void (*)())thread2, 0); 
    swapcontext(&main_context, &thread_context);
    makecontext(&thread_context, (void (*)())thread3, 0);
    swapcontext(&main_context, &thread_context);
    return 0;
}

实现的思路如下:

定义两个ucontext_t变量,main_context用来存放main函数上下文,thread_context用来存放线程上下文。首先要用getcontext取当前上下文到thread_context,方便我们后续对它进行操作,注意,不先取当前上下文而是用其他方式进行初始化,包括用new,都会引发段错误!这背后的原因大概是ucontext_t本身是个结构体,没有提供足够安全的构造函数,所以我们要用现成的main函数提供的上下文来改造。同样会引发段错误的还有不给栈空间赋值,这里用char的原因在于ucontext_t本身的大小我们并不确定,为了避免bus error,我们选择了每个元素只有一个字节的char数组。另外,char数组也不能用new,否则就不是栈空间了。而且指定栈空间之后,也是一定要指定大小,否则也会引发段错误。ss_flags倒是无所谓,是个指定栈增长方向的,默认情况下是不会报错的。。

上面主要是会引发段错误的一些情况,下面简要说下思路。将thread_context的后继(这里使用其内部指针元素uc_link实现)设置为main_context,这样每次该线程执行完之后都会返回main函数中,然后简单调用makecontext函数将对应函数设定在thread_context处就可以正常使用了,线程运行结束后会自动返回到main中。这里其实也是一开始就要将当前上下文保存的原因了——保证栈空间在main函数开始栈空间之后,使用main函数的栈空间中的资源。因为原先协程就是在线程内运行的,原本线程切换我们用协程来切换,但一个线程还是对应一个协程,这是栈空间上的对应。

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

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

相关文章

毫米波雷达原理(含代码)(含ARS548 4D毫米波雷达数据demo和可视化视频)

毫米波雷达原理 1. 传统毫米波雷达1.1 雷达工作原理1.2 单目标距离估计1.3 单目标速度估计1.4 单目标角度估计1.5 多目标距离估计1.6 多目标速度估计1.7多目标角度估计1.7 总结 3. FMCW雷达数据处理算法4. 毫米波雷达的目标解析(含python代码)5. ARS548 4D毫米波雷达数据demo(含…

最新AI创作系统,ChatGPT商业运营系统网站源码,SparkAi-v6.5.0,Ai绘画/GPTs应用,文档对话

一、文章前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持…

MySQL商城数据库88张表结构(46—50)

46、消息队列表 CREATE TABLE dingchengyu消息队列表 (id int(11) NOT NULL AUTO_INCREMENT COMMENT 序号,userId int(11) DEFAULT NULL COMMENT 用户id,msgTtype tinyint(4) DEFAULT 0 COMMENT 消息类型,createTime datetime DEFAULT NULL COMMENT 创建时间,sendTime datetim…

数据结构------栈的介绍和实现

目录 1.栈的一些初步认识 2.栈的实现 3.相关的函数介绍 &#xff08;1&#xff09;栈的初始化 &#xff08;2&#xff09;栈的销毁 &#xff08;3&#xff09;栈的数据插入 &#xff08;6&#xff09;判断是否为空 &#xff08;7&#xff09;栈的大小 4.栈的实现完整…

【全网首出】npm run serve报错 Expression: thread_id_key != 0x7777

总结 困扰了一天&#xff01;&#xff01;&#xff01;一直以为是自己哪里配置错了&#xff0c; 结果最后发现是node.js官方的问题&#xff0c; Node.js v16.x版本的fibers.node被弃用 本文阅读大概&#xff1a;3min #npm run serve时就报错 #找了一天的文章&#xff0c;找不…

【前端学习——call和apply函数】

call()和apply()这两个方法的作用可以简单归纳为改变this指向&#xff0c;从而让我们的this指向不在是谁调用了函数就指向谁。 call()方法的作用和 apply() 方法类似&#xff0c;区别就是call()方法接受的是参数列表&#xff0c;而apply()方法接受的是一个参数数组。 https:/…

HTML_CSS学习:常用文本属性

一、文本颜色 相关代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>文本颜色</title><style>div{font-size: 90px;}.atguigu1{color: #238c20;}.atguigu2{color: rgb(2…

【数据结构(邓俊辉)学习笔记】向量06——位图

文章目录 0.概述1.结构2.实现3. 应用3.1 去重3.2 筛法 0.概述 位图&#xff08;Bitmap&#xff09;是一种特殊的序列结构&#xff0c;可用以动态地表示由一组&#xff08;无符号&#xff09;整数构成的集合。 test() 判断k 是否存在集合S中。set() 将k 加入到集合S中。clear…

视频编辑软件pitivi基本功之将三个相关视频合并成一个视频

视频编辑软件pitivi基本功之将三个相关视频合并成一个视频 一、素材来源&#xff1a;网站下载 到http://cpc.people.com.cn/GB/67481/435238/437822/437828/437900/index.html下载以下三个视频&#xff0c;鼠标右击视频——另存视频为 庆祝中国共产党成立100周年大会即将开始—…

深度学习:基于TensorFlow、Keras,使用长短期记忆神经网络模型(LSTM)对Microsoft股票进行预测分析

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

R语言数据探索和分析7-使用随机森林模型对中国GDP及其影响因素分析

一、研究背景和意义 国内生产总值&#xff08;GDP&#xff09;是宏观经济领域中最为关注的经济统计数据之一&#xff0c;它反映了一个国家或地区在一定时期内所创造的所有最终商品和服务的总价值。GDP的增长率不仅仅是一个国家经济健康状况的关键指标&#xff0c;还直接关系到…

Web前端一套全部清晰 ⑥ day4 CSS.1 基础选择器、文字控制属性

后来的我不在抱怨 所有的事与愿违都是我能力或者判断力不足 仅此而已 —— 24.5.1 一、CSS定义 1. 将CSS放在html文件的<style>标签中 层叠样式表(Cascading style Sheets&#xff0c;缩写为 CSS)&#xff0c;是一种 样式表 语言&#xff0c;用来描述 HTML 文档的呈现(美…

软件应用开发安全设计指南

1.1 应用系统架构安全设计要求 设计时要充分考虑到系统架构的稳固性、可维护性和可扩展性&#xff0c;以确保系统在面对各种安全威胁时能够稳定运行。 在设计系统架构时&#xff0c;要充分考虑各种安全威胁&#xff0c;如DDoS攻击、SQL注入、跨站脚本攻击&#xff08;XSS&…

Github 2024-05-03 Java开源项目日报 Top9

根据Github Trendings的统计,今日(2024-05-03统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目9Kotlin项目1C++项目1libGDX: 跨平台Java游戏开发框架 创建周期:4284 天开发语言:Java, C++协议类型:Apache License 2.0Star数量:2…

如何在Android设备上恢复丢失的照片

Android手机或平板电脑上的照片丢失了&#xff1f;不要惊慌&#xff0c;您也许可以恢复它们。 由于我们的大量数据和日常生活都存储在一台设备上&#xff0c;有时将所有照片存储在本地的 Android 智能手机或平板电脑上可能是一项冒险的工作。无论是通过事故&#xff08;损坏、…

【JVM】从硬件层面和应用层面的有序性和可见性,到Java的volatile和synchronized

Java的关键字volatile保证了有序性和可见性&#xff0c;这里我试着从底层开始讲一下有序性和可见性。 一&#xff0c;一致性 数据如果同时被两个cpu读取了&#xff0c;如何保证数据的一致性&#xff1f;或者换句话说&#xff0c;cpu1改了数据&#xff0c;cpu2的数据就成了无效…

【C++】深入剖析C++11 initializer_list 新的类功能 可变模板参数

目录 一、std::initializer_list 1、std::initializer_list是什么类型 2、std::initializer_list 的应用场景 ①给自定义容器赋值 ② 传递同类型的数据集合 二、新的类功能 1、默认成员函数 2、关键字default 3、关键字delete 三、可变参数模板 一、std::initialize…

C++仿函数周边及包装器

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

RunnerGo四月更新:强化UI自动化测试与UI录制插件功能

RunnerGo最近更新的 UI自动化测试和UI录制插件可以让测试人员更高效地布置UI自动化场景。这次优化升级的插件录制能力&#xff0c;可以更准确的定位元素并执行步骤&#xff0c;并增加了局部截图功能&#xff0c;准确查看定位的元素位置等。 UI插件V2.0介绍 接下来&#xff0c;让…

python基础算法题0502

数字反转 无论是字符串反转还是数字反转&#xff0c;其实都一样。 需求 代码 class Solution:def reverse(self, x: int) -> int:if 0 < x < 2 ** 31 - 1:m str(x)[::-1]if int(m)<2**31-1:return int(m)else:return 0if 0 > x > -2 ** 31:y -xn str(y…