关于 C/C++ 1Z(17)开源项目 openppp2 协同程式切换工作流

下述为开源项目 openppp2(github)构建工作在 C/C++ 17 的 stackful 有栈协同程式的工作流切换示意图:

在 openppp2 之中采用人工手动方式管理协同程式之间的切换,每个中断过程只是保存线程栈信息(如寄存器、当前#PC EIP)并且JMP相对寄存器长跳转到其它协同程序当前EIP位置。

它(协同程式)的切换开销是最小的,速度几乎是无损最快的,这好比人们在 C/C++ 之中用 “函数指针(地址标识符)” 去指向一个 C/C++ 函数,且通过函数指针进行调用,它本身就是长跳转的一种,区别只是少做了线程信息保存,及下个协同程序线程信息恢复的动作。

当然协同程式有一些是通过操作系统核心库API构建的有栈协同程式。

例如:

1、Windows NT平台使用:GetThreadContext、SetThreadContext 函数来构建的有栈协同切换。

2、Linux 平台上使用 ucontext.h 库函数:getcontext、setcontext、makecontext、swapcontext 函数来构建的有栈协同切换。

那么:此类有栈协同程式切换效能的确不行,某些人不要张口就来,不是真懂别乱逼逼。

但通过汇编语言构建的有栈协同程式,几乎不存在性能问题,单线程挂载百万个、千万个协同程式都可以,只要母鸡内存足够大,即可。

stackless 有栈协同程式不要来碰瓷效能,比协同程式切换效能,stackless 真不配跟 stackfull 协同程式比切换效能,若我们不懂这两个协同程式怎么实现的,还真有可能被XX们忽悠住,但可惜我们了解这两类协同程式底层是怎么构建的,当然也自行实现构建过两者,所以在这块还是有一定心得发言权的。

从上述的协同程式示意图之中,可以清晰的看到,在中断某一个协同程式时,会直接切换CPU到下一个协同程式(若有,否则为协同程式结束),而不是还会等待执行到。

但这有一个缺点,一旦某一个协同不按照正确流程切换,那么就会导致协程 deadline 问题,出现致命性故障,如流程中断、内存泄漏等问题层出不穷。

欲兼容性解决这类问题,那么必须引入一个协同程式调度器,但这并非必须的,长长构建多线程并行程式、对于协同程序非常了解的童鞋并不需要,因为这种调度切换的低级问题,不会在它们身上发生。

对于 C/C++ 编程语言来说首先采用 stackful 有栈协同程式,而不是使用 stackless 协同程式,即便是 C/C++ 20 标准提供的 stackless 协同程式,或者早前基于 boost 库提供的 stackless 协同程式。

关于在 go 语言之中,go 开启一个新的 stackless 协同程式,并不意味着立即运行,go 开发人员适用编译器关键字 go 开启新协同程式,能否立即运行取决于以下条件。

在 go 运行时仅只有单线程的情况下,它无法立即运行,而是需要等待当前正在执行协同程序到中断位置或结束时才运行,多线程情况下取决于那个闲置线程先获取到事件(基于系统 epoll、iocp、kqueue 构建的EDSM事件驱动状态机,运行时调度器)。

了解关于我们对于协程相关的一些看法,可以参见本人的以下文章:

C/C++ 如何正确的切换协同程序?(基于协程的并行架构)_c++怎么切换运行程序-CSDN博客

stackless or stackfull 协同程式(协程)?_boost stackless-CSDN博客

灌水玩玩 ChatGPT AIGC生成的有栈协同程序实现(例子)_任务协同 aigc-CSDN博客

C/C++ 11/14/17 有栈式协同程式的基础框架类库【关于】_c++11实现有栈对称协程库-CSDN博客

C++ 20标准协同程序(协程)基于编译器展开的 stackless 协程。-CSDN博客

关于 Go 协同程序(Coroutines 协程)、Go 汇编及一些注意事项。-CSDN博客

童鞋们可以好好理解在这些文章之中,我们关于协同程式的一些看法,那么童鞋们会对于协同程式有更深入的理解,不要去现在鱼龙混杂的逼乎(知乎)上看那些莫名其妙的想法及观点,国内技术相关这块大概就博客园、CSDN博客、看雪论坛比较好一点,其它建议还是算了把。

下述代码为 Linux 平台基于 ucontext.h 函数库实现的有栈协同程式切换(它很简明):

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

#define STACK_SIZE 1024*64

ucontext_t mainContext, coroContext;

void coroutineFunction() {
    std::cout << "Coroutine started" << std::endl;
    
    // 切换回主上下文
    swapcontext(&coroContext, &mainContext);

    std::cout << "Coroutine resumed" << std::endl;
}

int main() {
    char stack[STACK_SIZE];

    getcontext(&coroContext);
    coroContext.uc_link = &mainContext;
    coroContext.uc_stack.ss_sp = stack;
    coroContext.uc_stack.ss_size = sizeof(stack);

    makecontext(&coroContext, (void (*)())coroutineFunction, 0);

    std::cout << "Main started" << std::endl;
    
    // 切换到协程上下文
    swapcontext(&mainContext, &coroContext);

    std::cout << "Main resumed" << std::endl;

    return 0;
}

相关推荐

  1. 工作实践-11关于uniapp切换账号登录失败问题

    2024-04-02 20:44:01       9 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-02 20:44:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-02 20:44:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-02 20:44:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-02 20:44:01       18 阅读

热门阅读

  1. Android invalidate、postInvalidate、requestLayout的区别

    2024-04-02 20:44:01       12 阅读
  2. FastAPI Web框架教程 第11章 请求响应的进阶用法

    2024-04-02 20:44:01       14 阅读
  3. 蓝桥杯刷题记录之质数

    2024-04-02 20:44:01       11 阅读
  4. nignx的功能包括哪些

    2024-04-02 20:44:01       13 阅读
  5. LeetCode226.翻转二叉树

    2024-04-02 20:44:01       12 阅读
  6. 洛谷 B3918 [语言月赛 202401] 图像变换

    2024-04-02 20:44:01       15 阅读