C语言中协程(coroutine)实现

2023-11-11

C语言协程库实现说明

代码实现

1. 当前支持的功能概览

1.1 创建任意数量协程并在协程中yield

#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>

#include "gtest/gtest.h"

#include "coroutine.h"
#include "asyncio.h"

static int g_run_sums = 0;
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;

static void co(void *args)
{
    int num = *((int *)args);

    pthread_mutex_lock(&g_lock);
    g_run_sums += num;  // 每个协程分别递增全局变量
    pthread_mutex_unlock(&g_lock);

    printf("coroutine:%d begin...\r\n", num);
    coroutine_yield();
    printf("coroutine:%d ended...\r\n", num);
}

TEST(coroutine_create, three_cos_run_success) 
{
    int a = 1, b = 2, c = 3; //a, b, c三个协程依次对全局变量g_run_sums增加1,2,3
    coroutine_init();
    coroutine_create(co, (void*)&a);
    coroutine_create(co, (void*)&b);
    coroutine_create(co, (void*)&c);

    coroutine_loop();

    EXPECT_EQ(g_run_sums, 6); // 最终全局变量为6
}

1.2 创建2个协程,其中一个睡眠100ms

  
#include <stdio.h>
#include <stdlib.h>

#include <pthread.h>

#include "gtest/gtest.h"

#include "coroutine.h"
#include "asyncio.h"

static int seq = 0;
static int co_seq[2] = {0};

static void co_sleep(void *args)
{
    printf("co sleep begin.\r\n");
    asyncio_sleep(100); // 调用asyncio api睡眠100ms.
    co_seq[seq++] = 100;
    printf("co sleep end.\r\n");
}

static void co_nosleep(void *args)
{
    printf("co no sleep begin.\r\n");
    co_seq[seq++] = 200; 
    printf("co no sleep end.\r\n");
}

TEST(coroutine_run, co_sleep) 
{
    coroutine_init();
    coroutine_create(co_sleep, NULL);
    coroutine_create(co_nosleep, NULL);

    coroutine_loop();

    EXPECT_EQ(co_seq[0], 200); //验证未睡眠协程先完成了执行
    EXPECT_EQ(co_seq[1], 100);
}

2. COROUTINE状态

corouting有3种状态, RUNNALBE, RUNNING, WAITING.

  • WAITING: corouting暂停并等待一些条件以继续运行.比如:sleep, 系统调用,同步操作(原子或锁操作),这种延迟是性能差的根源.
  • RUNNABLE: corouting具备运行条件正在等待分配processor以执行指令
  • RUNNING: corouting已经分配到processor,并正在上面执行指令

3. 调度器实现

  • processor_t: 管理一个实际的os线程,内部使用队列维护分配给它的coroutine,使用epoll进行事件循环.
  • processors_t: 全局processor_t管理器,创建时会按照实际的cpu个数创建对应的processor_t, 它负责将新协程按照一定算法分配给某个processor_t.
    同时负责检测没有任何协程时退出进程.

3.1 主进程何时退出

当没有任何协程存在时,则退出主进程.

3.1.1 实现原理

模拟实现了Golang中的waitGroup, 用于等待所有协程退出.新协程创建会调用waitGroup_add,协程结束会调用waitGroup_del,当waitGroup空闲时
则说明所有协程都已经退出.

3.2 processor_t调度主循环处理

    1. 循环遍历coroutine就绪队列,依次运行coroutine.
    1. 如果没有就绪的coroutine且本地队列上coroutine个数为0,则进行步骤3,否则进行步骤4
    1. 通过条件变量等待分配新的coroutine,如果收到了条件变量且是退出指令,则进行步骤5,否则进行步骤1.
    1. 本地队列还有coroutine,但是coroutine都在等待事件,则进行事件循环以等待指定事件的到来,这样就会有coroutine就绪,进行步骤1.
    1. 退出主循环

3.3 上下文切换实现

3.3.1 原理

corouting在用户态进行上下文切换,上下文主要包括:堆栈,寄存器(IP, SP等).
上下文切换主要通过<ucontext.h>中定义的getcontext, setcontext, makecontext, swapcontext实现.

3.3.2 上下文切换时机

  • corouting主动调用coroutine_yield(),如果有其它待运行的coroutine则主动让出processor_t
  • 协程中调用了协程库asyncio API,则由API选择合适的时机进行上下文切换,如调用阻塞API,如corouting_sleep.
  • 如果你在协程中执行cpu密集型操作或直接调用阻塞的C api,那么会影响当前processor的调度和运行.
3.3.3 堆栈使用
  • 每个processor_t维护1M的堆栈空间M
  • 协程刚创建时为RUNNABLE状态,此时直接使用M作为堆栈,当协程需要放权时保存当前堆栈到协程自己的空间M0
  • 协程恢复运行时,将保存的堆栈M0还原到M中继续运行

这样每个协程最大都可以有1M的堆栈空间,且堆栈空间能够按需分配,每个processor_t上堆栈的消耗为所有协程
实际使用的堆栈内存+1M.

如果不这样实现,每个协程都需要初始分配1M空间,消耗为协程个数*1M.

4. 异步操作协程库asyncio实现

  • asyncio提供一系列api用于在协程环境中编写并发代码.
  • asyncio是coroutine框架提供的api可以用于实现高性能网络服务器,数据库连接库,分布式任务队列等.
  • asyncio适合IO密集型和高级别的结构化网络程序

4.1 当前支持的API

  • coroutine_sleep(long delay_ms): 当前协程休眠指定ms.
  • coroutine_yield(): 当前协程主动放权给其它就绪协程,由调度器选择合适时机再重新调度.

5. 代码说明

5.1 编译代码


cmake -H. -Bbuild
cmake --build ./build

5.2 运行测试

cd build
ctest --verbose
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言中协程(coroutine)实现 的相关文章

  • shell脚本整段注释

    摘自 http zhidao baidu com link url XmCCZmfluRe6n8TjPRKJTx4GGOUPSGX1VNBm euqGdpKGpveTESxC0HL90UBNT5nZCvmvfq2oIJdP3JO5EoPSq
  • 《Android网络请求篇》MyHttpUtils一个非常好用的异步网络请求框架

    Android网络请求篇 MyHttpUtils一个非常好用的异步网络请求框架 最新版介绍看这里 gt 这是一个使用策略模式和构建模式设计的网络请求框架 去看看吧 倾力之作 android轻量级网络请求框架MyHttputils2 1 6
  • 网络同步与异步概念整理

    在网络同步中 有两种同步方式 分别为同步与异步 同步的操作指的是 当所有的操作请求都做完 才将结果返回给用户 用户才能进行下一个操作 这样就会让用户有一种卡顿的感觉 因为需要等待上一步操作的执行结果 异步操作指的是 用户的操作之间不需要进行
  • 理解javascript的同步与异步模式

    你可能知道 Javascript语言的执行环境是 单线程 single thread 所谓 单线程 就是指一次只能完成一件任务 如果有多个任务 就必须排队 前面一个任务完成 再执行后面一个任务 以此类推 这种模式的好处是实现起来比较简单 执
  • Lua中的协程Coroutine

    一 协程是什么 1 线程 首先复习一下多线程 我们都知道线程 Thread 每一个线程都代表一个执行序列 当我们在程序中创建多线程的时候 看起来 同一时刻多个线程是同时执行的 不过实质上多个线程是并发的 因为只有一个CPU 所以实质上同一个
  • 关于httpurlconnection getcontentlength返回值为-1的问题

    在做AsyncTask异步任务读取网络数据的时候 发现了httpUrlConnection的获取数据内容长度 connection getContentLength 总是为 1 导致进度条一直为灰色状态 预期结果应如图所示 而现在的效果是这
  • 小白学协程笔记2-c语言实现协程-2021-2-10

    文章目录 前言 一 c语言中协程切换方式 二 使用setjmp 和 longjmp实现协程切换 1 setjmp和longjmp函数简介 2 协程实现 三 使用switch case实现协程切换 1 switch case小技巧 2 协程实
  • 限制可以在范围内运行的协程的最大数量

    我正在将当前的应用程序从 Java 翻译为 Kotlin 并且遇到了这个问题 java实现过去使用线程从服务器传输数据 它将创建大约 100 个不同的线程来请求数据 但据我所知 一次运行的线程不会超过 4 个 其他线程会等待线程完成后再开始
  • Kotlin 协程中的 launch/join 和 async/await 有什么区别

    In the kotlinx coroutines库 您可以使用以下任一方式启动新的协程launch with join or async with await 它们之间有什么区别 launch习惯于即发即忘协程 这就像开始一个新线程 如果
  • 进阶之Kotin协程原理和启动方式详细讲解(优雅使用协程)

    协程就是方法调用封装成类线程的API 方法调用当然比线程切换轻量 而封装成类线程的API后 它形似线程 可手动启动 有各种运行状态 能够协作工作 能够并发执行 前言 kotlin的协程在初学者看来是一个很神奇的东西 居然能做到用同步的代码块
  • 什么是挂起协程?

    我是初学者学习coroutines 不完全是 但我对什么是有一点了解coroutine is The suspend function也很难 但是有一点理解 我正在一步步学习 但有些地方我不明白 That s suspendCoroutin
  • Kotlin 协程 future 等待超时(不可取消)

    鉴于我们有一个CompletableFuture f 在 kotlin 可挂起范围内我们可以调用f await 我们将暂停直到完成 我在使用签名实现类似功能时遇到问题f await t 必须暂停最大t毫秒或如果 future 在该持续时间内
  • 如何仅在协程完成后才继续执行该功能?

    void Generate StartCoroutine FallDelayCoroutine print time3 Time time IEnumerator FallDelayCoroutine print time1 Time ti
  • 如何在 Unity 协程中通过引用局部变量?

    我有一些函数可以接受 Enemy 实例并更改其字段之一 敌人类别有一些基本字段 如速度 伤害 攻击范围 每个函数只存储敌人的一个正常值 然后将当前字段更改为某个值一段时间 然后将其更改回正常状态 我在 Unity 中编写代码并使用 Coro
  • 挂起函数“callGetApi”只能从协程或另一个挂起函数调用

    我正在从 onCreate 调用挂起函数 override fun onCreate savedInstanceState Bundle callGetApi 暂停的功能是 suspend fun callGetApi 但错误出现了挂起函数
  • 当协程包含协程延迟时,如何对协程进行单元测试?

    当我在视图模型中添加协程延迟 时 代码的其余部分将不会被执行 这是我的演示代码 class SimpleViewModel ViewModel CoroutineScope override val coroutineContext Cor
  • 从 Python 中的“with”块中进行屈服是否安全(以及为什么)?

    协程和资源获取的结合似乎可能会产生一些意想不到的 或不直观的 后果 基本问题是这样的方法是否有效 def coroutine with open path r as fh for line in fh yield line 确实如此 你可以
  • 比使用“任务/生产/消费”更好的方法将惰性集合表示为协程

    使用起来非常方便Tasks表达一个惰性集合 生成器 Eg function fib Task do prev prev 0 prev 1 produce prev while true cur prev prev prev produce
  • 将回调地狱转换为延迟对象

    背景 所以 我有一个相当大的项目 有很多 API 函数 我正在考虑完全转向协程 但由于它们的实现方式是Callback并不是Deferred 我无法有效地使用它们 例如 我想做apiCallOne apiCallTwo and apiCal
  • Lua :: 如何编写加载多个CPU的简单程序?

    我还无法用 Lua 编写一个可以加载多个 CPU 的程序 自从Lua通过协程支持这个概念 http www lua org pil 9 4 html 我相信这是可以实现的 我失败的原因可能是以下之一 这在Lua中是不可能的 我写不出来 an

随机推荐

  • esBuild + SWC 构建 TS 项目

    1 esBuild 介绍 在 esbuild 的官方介绍中打包 threejs 只需要 0 37 秒 Esbuild 是一个非常新的模块打包工具 它提供了与 Webpack Rollup Parcel 等工具 相似 的资源打包能力 却有着高
  • 两个3*3的卷积核替代5*5(三个3*3卷积核替代7*7)分析

    文章目录 为什么一个5x5的卷积核可以用两个3x3的卷积核来替代 一个5 5卷积 两个3 3卷积核 为什么一个7x7的卷积核可以用三个个3x3的卷积核来替代 一个7 7卷积 三个3 3卷积核 优点总结 为什么一个5x5的卷积核可以用两个3x
  • 输入一个字符串,把一个字符串的字符逆序输出

    package com qf day4 import java util Scanner public class Test29 public static void main String args 把一个字符串的字符逆序输出 Scann
  • 可能是最详细的React组件库搭建总结

    可能是最详细的React组件库搭建总结 概览 本文包含以下内容 prepare 组件库前期开发准备工作 eslint commit lint typescript等等 dev 使用docz进行开发调试以及文档编写 build umd cjs
  • TeamViewer账号未激活问题

    出现如下问题 解决步骤 1 点击重新发送电子邮件 2 网页上登录 3 QQ邮箱里面添加设备信任 4 返回登录页面再重新 刷新重新输入登录信息登录 然后重新发送电子邮件到qq邮箱 5 点击激活 6 登录 gt gt gt 不要现在登录 gt
  • JAVA各种加密与解密方式

    之前有兴趣研究了一下java的加密与解密的方法 发现市面上有好多种加密解密方式 在这里整理了一下 目录 1 BASE64加密 解密 2 MD5 Message Digest Algorithm 加密 3 DES Data Encryptio
  • 将 pip 的默认源修改为阿里源(Windows 版)

    背景 由于 python 自带的源下载速度非常慢 特别是安装一些库的时候 甚至有时会失败 临时替换 临时替换直接使用以下命令即可 pip install 包名 i 源地址 例如 pip install numpy i http mirror
  • Visual Studio无法打开源文件错误

    在写用Visual Studio跨平台Linux项目的时候 遇到了无法打开源文件错误 主要原因是include目录里面没有这些文件 解决方法是 把Linux的 usr include目录压缩下载在本地 zip r usr include z
  • js 导出excel详解

    一 需要安装 npm install xlsx style Blob js 和Export2Excel js 在网上搜都可以找到的 而Export2Excel我做了修改 代码如下 带 的都是我改动的地方 Export2Excel js es
  • SQL中CASE的用法

    在SQL中 CASE语句是一种条件表达式 用于根据条件执行不同的操作 它有两种形式 简单CASE表达式和搜索CASE表达式 简单CASE表达式的语法如下 CASE expression WHEN value1 THEN result1 WH
  • GEO数据下载及处理详细过程

    GEO2R 如果出现提示 请指定GEO系列加入和平台 单击 定义组 并输入您计划比较的样品组的名称 例如测试和控制 将样本分配给每个组 突出显示Sample行 然后单击组名称以将这些Samples分配给该组 使用样本元数据 标题 源和特征
  • C++ 强制类型转换(const_cast/reinterpret_cast)使用详解

    一 const cast用法 const cast lt new type gt expression 用于转换指针或引用 可以去掉类型的const属性 在c 参考文档网站上 const cast conversion cppreferen
  • 用力抱一下APP国际化

    APP国际化 说的直白应该也叫本土化或者本地化 如果你的应用上线到谷歌应用市场 那么应该做好本地化的支持 用来支持不同语言及地区的风俗习惯 当然也要结合公司拓展的海外市场需要 那么对于一款应用 至少应该做到多语言和多布局的支持 最近忙于阿拉
  • rocketmq顺序发送消息

    1 概念 严格顺序消息模式下 消费者收到的所有消息均是有顺序的 消息有序指的是可以按照消息的发送顺序来消费 FIFO RocketMQ可以严格的保证消息有序 可以分为分区有序或者全局有序 顺序消费的原理解析 在默认的情况下消息发送会采取Ro
  • (二)MySQL的安装、启动/停止/连接、卸载

    本篇魔镜为大家介绍MySQL的安装 启动 停止 客户端连接 卸载 安 装 在学习SQL语言时 我们总是避免不了使用关系型数据库管理系统 RDBMS 云端的RDBMS使用时比单机的RDBMS更麻烦 而在常用DBMS的安装等问题上 总是有小伙伴
  • h5 原生 ajax,原生ajax和axios取消请求实现

    原生ajax中取消请求的方法 const xhr new XMLHttpReques xhr open GET api xhr onreadstatechange gt if xhr readState 4 2 d 2 test xhr s
  • 怎么使用 js 动态生成海报?

    方案一 DOM gt canvas gt image 将目标 DOM 节点绘制到 canvas 画布 然后利用 canvas 相关的 API 以图片形式导出 可简单标记为绘制阶段和导出阶段两个步骤 绘制阶段 选择希望绘制的 DOM 节点 根
  • svn 恢复删除的文件

    本文转载至 http stackoverflow com questions 497670 whats a simple way to undelete a file in subversion If you just did svn rm
  • Retrofit 2.5框架使用与源码分析

    Retrofit 框架使用 请求内容与返回值 使用PostMan进行请求测试 请求 https api github com search repositories q android 返回值 Header 外链图片转存失败 源站可能有防盗
  • C语言中协程(coroutine)实现

    C语言协程库实现说明 代码实现 1 当前支持的功能概览 1 1 创建任意数量协程并在协程中yield include