仿制云风的协程库的接口设计,我花了一个下午加晚上的时间重构了之前写的协程库,提供的接口现在和云风大大的协程接口一模一样,都是仿制lua的非对称协程。我们依旧没有用ucontext.h组件(因为ucontext.h组件在osX下已经deprecated了,如果你加入sys/ucontext.h头文件,发现某些小型C协程库还能运行,纯属巧合,因为官方已经不维护这个组件了,以后很可能出错),我们的协程库可以运行在兼容X86平台的操作系统上,各种unix-like操作系统,windows操作系统都可以,不过得用gcc或者clang或者与之兼容的mingw编译工具编译出32位的程序运行,不能用vs或者vc++系列,因为我们用了gasm内联汇编格式,当然你可以稍微改下几行汇编就能移植到vs或者vc++版本。在多线程环境下运行协程时,不同线程不能共享协程组,也就是说任意两个协程,若它们属于不同的线程,那么它们得属于不同的协程组。这是基本编程准则。比如想在POSIX多线程接口里头用我们的协程库时候,你可以这么用:

 1 #include <all needed>
2
3 hello(schedular *s)
4 {
5 coroutine_new(s, otherfunc, args); /* 在s协程组里头创建一个协程, 这个s可能是S或S1等等 */
6 ... do something
7 }
8
9 foo(...)
10 {
11 schedular *S1 =coroutine_open(); /* 分配一个协程组S1,这个只能属于线程tid */
12 int co = coroutine_new(S1, hello, S1); /* 创建一个协程 */
13 ... do something
14 coroutine_resume(S1,co); /* 调用本地协程组里头的co协程 */
15 ... do something
16 coroutine_close(&S1); /* 释放S1协程组 */
17 }
18
19 main{
20 schedular *S =coroutine_open(); /* 分配一个协程组S,这个只能属于主线程 */
21 pthread_create(tid, ..., foo, ...); /* 创建Posix线程 */
22 int co = coroutine_new(S, hello, S); /* 创建一个协程 */
23 ... do something
24 coroutine_resume(S,co); /* 调用本地协程组里头的co协程 */
25 ... do something
26 coroutine_close(&S); /* 释放S协程组 */
27 pthread_join(tid, ...); /* 等待并回收线程资源 */
28 }

  如果要想不同线程间的协程通讯,得用操作系统各自的API,比如用共享内存方式来实现,我们没有实现类似goroutine的channel。协程库为共享栈模式,一个协程组可以容纳最多一百万个协程,每个协程共用128Kbytes栈空间,我用top命令监测了一下运行一百万个协程的测试程序,此时该测试程序内存占用峰值为280M左右,可以推算每个协程内存占用峰值为280bytes左右。可以推断,如果运行一千万个协程,我们至少需要10个协程组,每个协程组280M,一共2800M = 2.7G左右 = 4G总理论空间 - 1G内核空间,所以我们的协程库所能支持的最大协程数是1000万,这是理论上限。用gprof测试程序性能得知,2999997次协程切换共用0.39秒,每次切换时间在130ns左右。最重要的是,我们的协程库所有的源码加起来大概只有400行左右,这还包括了微量的注释和头文件。如果能够移植到X64版本,那么我们的协程库可以轻松支持千万数量的协程,可以在实际开发中使用,不再是单单只有教学意义的小玩意了。可惜的是在unix like的各种平台下,作为唯一的异步非阻塞IO组件,linux kernal实现的aio组件并不是那么成熟(可能要烂尾了),而且glibc中用线程+信号模拟的用户态aio组件也是有很多bug存在。异步操作与协程的结合果然还是在语言层面(做编译器前端)实现更好,而非在库上实现。

项目GitHub链接:https://github.com/Yuandong-Chen/coroutine/tree/ezco.v.0.0.1

后记:

  最近用setjmp.h组件,重构了项目,删去了汇编代码,把项目移植到了X64-macosx-clang版本(只兼容intel X64的处理器,osX操作系统以及Clang编译器,不兼容其他任何变化,包括Linux,GCC,X86等等),可以支持上亿个协程(思考下?为何?)。项目放在上面GitHub链接里头的默认版本内。到此为止,我们的协程完成度近似libconcurrency库。如何移植到X64-linux-gcc版本是一个问题,因为glibc里头,我们无法在jmp_buf数组中通过偏移量取出rip逻辑寄存器的取值。所以为了达到可移植性,我们还是得用汇编写一个自己的setjmp/longjmp函数,其实很多协程库就是自己重写了setjmp/longjmp以满足兼容性。这样也有个坏处,那就是我们得写大量的汇编,对不同的C编译器,不同的操作系统,不同的CPU都得写一个对应的汇编版本,当然,可以通过内联汇编的方式在一定程度上减轻一点点工作量。这种兼容性的体力活我就不去干了。

  这里给出为何能支持上亿协程的答案:很简单,我们以1000万个协程占4G空间估计,那么64位机如果有128G内存的话,1000万*128/4 = 3亿左右的协程并发。当然,我想没人会用3亿协程并发,因为即便是8核16线程,负载为300000000/(16) = 1.8亿协程/线程,除非你有超级计算机,那么才可以做到几百协程/线程。

进一步需要做的:

1)彻底放弃共享栈,每个协程重新拥有自己独立的栈空间。因为x64下的虚拟内存足够大了,共享栈带来的优势太小,我们根本不需要几亿个协程并发,但是我们需要他们执行的足够快。

2)放弃lua的resume-yield协程模型,改用erlang的spawn模型,从而能够利用多核带来的并行执行的优势。

3)添加协程间的channel机制(一种消息传递机制)。

总的说来,我们相当于要把erlang的轻量级进程这部分做成C语言库。

写个百万级别full-stack小型协程库——原理介绍的更多相关文章

  1. C高级 跨平台协程库

    1.0 协程库引言 协程对于上层语言还是比较常见的. 例如C# 中 yield retrun, lua 中 coroutine.yield 等来构建同步并发的程序. 本文就是探讨如何从底层实现开发级别 ...

  2. 一个“蝇量级” C 语言协程库

    协程(coroutine)顾名思义就是“协作的例程”(co-operative routines).跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程 ...

  3. 协程库st(state threads library)原理解析

    协程库state threads library(以下简称st)是一个基于setjmp/longjmp实现的C语言版用户线程库或协程库(user level thread). 这里有一个基本的协程例子 ...

  4. CPU的最小执行单位是线程,协程不需要qt支持...直接用现成的协程库就行了

    协程也就在I/O操作上才有优势,Qt事件循环,本事很多I/O已经是异步了,利用好异步(虽然都说异步有点反人类思维).因为CPU的执行最小单位是线程,协程也只是在其之上又调度而已. 我的意思是利用好异步 ...

  5. 二、深入asyncio协程(任务对象,协程调用原理,协程并发)

      由于才开始写博客,之前都是写笔记自己看,所以可能会存在表述不清,过于啰嗦等各种各样的问题,有什么疑问或者批评欢迎在评论区留言. 如果你初次接触协程,请先阅读上一篇文章初识asyncio协程对asy ...

  6. 协程的原理(Coroutine Theory)

    原文链接:https://lewissbaker.github.io/2017/09/25/coroutine-theory This is the first of a series of post ...

  7. Stackful 协程库 libgo(单机100万协程)

    libgo 是一个使用 C++ 编写的协作式调度的stackful协程库, 同时也是一个强大的并行编程库. 设计之初是为高并发分布式Linux服务端程序开发提供底层框架支持,可以让链接进程序的同步的第 ...

  8. 基于ASIO的协程库orchid简介

    什么是orchid? orchid是一个构建于boost库基础上的C++库,类似于python下的gevent/eventlet,为用户提供基于协程的并发模型. 什么是协程: 协程,即协作式程序,其思 ...

  9. libco协程库上下文切换原理详解

    缘起 libco 协程库在单个线程中实现了多个协程的创建和切换.按照我们通常的编程思路,单个线程中的程序执行流程通常是顺序的,调用函数同样也是 “调用——返回”,每次都是从函数的入口处开始执行.而li ...

随机推荐

  1. lua 模块

    lua 模块 概述 lua 模块类似于封装库 将相应功能封装为一个模块, 可以按照面向对象中的类定义去理解和使用 使用 模块文件示例程序 mod = {} mod.constant = "模 ...

  2. AngularJS创建新指令 - 函数功能

    首先先介绍下AngularJS指令下的几种函数 Link函数和Scope 指令生成出的模板其实没有太多意义,除非它在特定的scope下编译.默认情况下,指令并不会创建新的子scope.更多的,它使用父 ...

  3. 使用vue-cli构建多页面应用+vux(三)

    上节中,我们成功的将vue-cli改造成了多入口,既然用了上简单的脚手架,那就希望用个合适的UI组件,去搜索了几个以后,最后选择了使用vux 贴上其vux的github地址  https://gith ...

  4. Spring+SpringMVC+MyBatis+easyUI整合优化篇(十三)数据层优化-表规范、索引优化

    本文提要 最近写的几篇文章都是关于数据层优化方面的,这几天也在想还有哪些地方可以优化改进,结合日志和项目代码发现,关于数据层的优化,还是有几个方面可以继续修改的,代码方面,整合了druid数据源也开启 ...

  5. poj2104 Kth-Number

    Description You are working for Macrohard company in data structures department. After failing your ...

  6. salesforce零基础学习(七十一)级联表DML操作

    曾经做项目没有考虑那么多,对于级联表操作都是正常的一步一步操作,没有考虑过失败情况,最近项目遇见了失败的情况,导致碰到了相应的情况,特此mark一下,免得后期继续踩坑. 需求如下:新建页面,页面中包含 ...

  7. H5前端框架推荐合集

    Ionic ionic 吧开发流程都帮你做好了,已经不再是单纯的UI框架,而是开发框架了,非常适合快速开发.基于angular2,丰富的UI组件,大大改进的编程模型, Semantic UI 中文官网 ...

  8. WF4.0以上使用代码完整自定义动态生成执行工作流Xaml文件

    给大家分享一下,如何完全使用代码自定义的创建生成工作流文件(用代码创建Xaml文件),并且动态加载运行所生成的工作流. 工作流生成后 在Xaml文件里的主要节点如下: 输入输出参数 <x:Mem ...

  9. Java设计模式随笔

    大家都知道Java23种设计模式,大神总结如下: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接 ...

  10. 1013 Realtime Status

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission( ...