仿制云风的协程库的接口设计,我花了一个下午加晚上的时间重构了之前写的协程库,提供的接口现在和云风大大的协程接口一模一样,都是仿制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. CSAcademy Beta Round #3 a-game

    题目连接 a-game 大意:有一个只包含A和B的字符串,两个人分别取这个串的子串,但是每一次取不能与之前取到过的子串有交集,最后谁取到的所有串中A的总数量少的判为胜.如果一样,则为平手. 给出这样的 ...

  2. Android SQLite与ListView的简单使用

    2017-04-25 初写博客有很多地方都有不足,希望各位大神给点建议. 回归主题,这次简单的给大家介绍一下Android SQLite与ListView的简单使用sqlite在上节中有介绍,所以在这 ...

  3. 基础才是重中之重~关于ThreadStatic和Quartz的一点渊源

    回到目录 ThreadStatic ThreadStatic是C#里的一个特性,它可以让你的字段在一个线程里有效,但你不能控制这个字段在何时被回收,即如果声明一个int32的字段为ThreadStat ...

  4. javascript动画毛爷爷满天飘

    var minSize=50;var maxSize=100;var newOn=200;var flakeColor="#fff";var flak=$("<di ...

  5. CF798 C. Mike and gcd problem

    /* CF798 C. Mike and gcd problem http://codeforces.com/contest/798/problem/C 数论 贪心 题意:如果一个数列的gcd值大于1 ...

  6. javascript 六种数据类型(一)

    js的数据类型和常见隐式转化逻辑. 一.六种数据类型 原始类型(基本类型):按值访问,可以操作保存在变量中实际的值.原始类型汇总中null和undefined比较特殊. 引用类型:引用类型的值是保存在 ...

  7. 热切换Log4j级别配置

    欢迎和大家交流技术相关问题:邮箱: jiangxinnju@163.com博客园地址: http://www.cnblogs.com/jiangxinnjuGitHub地址: https://gith ...

  8. [刷题]算法竞赛入门经典(第2版) 6-9/UVa127 - "Accordian" Patience

    题意:52张牌排一行,一旦出现任何一张牌与它左边的第一张或第三张"匹配",即花色或点数相同,则须立即将其移动到那张牌上面,将其覆盖.能执行以上移动的只有压在最上面的牌.直到最后没有 ...

  9. 用cv::Scalar来设置opencv中图片的颜色

    1 怎样使用cv::Scalar来设置opencv中的颜色 cv::Scalar的构造函数是cv::Scalar(v1, v2, v3, v4),前面的三个参数是依次设置BGR的,和RGB相反,第四个 ...

  10. python通过http请求发送soap报文进行webservice接口调用

    最近学习Python调用webservice 接口,开始的时候主要采用suds 的方式生产client调用,后来发现公司的短信接口采用的是soap报文来调用的,然后开始了谷歌,最后采用httplib ...