写个百万级别full-stack小型协程库——原理介绍
仿制云风的协程库的接口设计,我花了一个下午加晚上的时间重构了之前写的协程库,提供的接口现在和云风大大的协程接口一模一样,都是仿制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小型协程库——原理介绍的更多相关文章
- C高级 跨平台协程库
1.0 协程库引言 协程对于上层语言还是比较常见的. 例如C# 中 yield retrun, lua 中 coroutine.yield 等来构建同步并发的程序. 本文就是探讨如何从底层实现开发级别 ...
- 一个“蝇量级” C 语言协程库
协程(coroutine)顾名思义就是“协作的例程”(co-operative routines).跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程 ...
- 协程库st(state threads library)原理解析
协程库state threads library(以下简称st)是一个基于setjmp/longjmp实现的C语言版用户线程库或协程库(user level thread). 这里有一个基本的协程例子 ...
- CPU的最小执行单位是线程,协程不需要qt支持...直接用现成的协程库就行了
协程也就在I/O操作上才有优势,Qt事件循环,本事很多I/O已经是异步了,利用好异步(虽然都说异步有点反人类思维).因为CPU的执行最小单位是线程,协程也只是在其之上又调度而已. 我的意思是利用好异步 ...
- 二、深入asyncio协程(任务对象,协程调用原理,协程并发)
由于才开始写博客,之前都是写笔记自己看,所以可能会存在表述不清,过于啰嗦等各种各样的问题,有什么疑问或者批评欢迎在评论区留言. 如果你初次接触协程,请先阅读上一篇文章初识asyncio协程对asy ...
- 协程的原理(Coroutine Theory)
原文链接:https://lewissbaker.github.io/2017/09/25/coroutine-theory This is the first of a series of post ...
- Stackful 协程库 libgo(单机100万协程)
libgo 是一个使用 C++ 编写的协作式调度的stackful协程库, 同时也是一个强大的并行编程库. 设计之初是为高并发分布式Linux服务端程序开发提供底层框架支持,可以让链接进程序的同步的第 ...
- 基于ASIO的协程库orchid简介
什么是orchid? orchid是一个构建于boost库基础上的C++库,类似于python下的gevent/eventlet,为用户提供基于协程的并发模型. 什么是协程: 协程,即协作式程序,其思 ...
- libco协程库上下文切换原理详解
缘起 libco 协程库在单个线程中实现了多个协程的创建和切换.按照我们通常的编程思路,单个线程中的程序执行流程通常是顺序的,调用函数同样也是 “调用——返回”,每次都是从函数的入口处开始执行.而li ...
随机推荐
- NodeJS+express+mogondb学习笔记01
0.准备工作 安装nodejs环境 官网地址:https://nodejs.org/en/ 下载好了 直接一路安装 也没有什么可以说的 不得不说nodejs对于新手上手还是很友好的,再加上现在n ...
- 【转】JDBC学习笔记(4)——PreparedStatement的使用
转自:http://www.cnblogs.com/ysw-go/ PreparedStatement public interface PreparedStatement extends State ...
- CPP--借助神器VS理解内存存储
之前也有想了解这些,第一个不是学底层的不知道从何理解,第二个上网搜概念,大牛们三言两语就结束了,举得例子也比较复杂,对于非C方向的可能有点吃力,所以一直没理解. 今天偶然发现原来还要内存窗口之说,就慢 ...
- 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- PPT要你好看---读书笔记
PPT要你好看.主要是设计的思维. 下图,对于现阶段的我来说,收获最大的是毕业答辩PPT的制作. 以及整体的PPT制作思路.
- 页面性能优化的利器 — Timeline
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 陈泽钦,腾讯移动客户端工程师,目前就职于腾讯MIG移动互联网事业群,负责腾讯浏览服务TBS的X5内核业务. 1 ...
- Python生产环境部署(fastcgi,uwsgi)
Python部署web开发程序的几种方法 fastcgi ,通过flup模块来支持,在nginx里对应的配置指令是 fastcgi_pass http,nginx使用proxy_pass转发,这个要求 ...
- Docker学习总结(一)
<认识Docker> 不定期更新~~~~~~~ 历史区别: 13年之前:网络大多使用"协议栈堆叠"的形式进行开发,需要部署单一专有的服务器进行操作.包括(中间件,运行时 ...
- HDU4497GCD and LMC最大公约数与最小公倍数
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4497 题目大意: 求gcd(x,y,z)=G且lcm(x,y,z)=L的方法数. 题目分析: 起初这 ...
- 漫话JavaScript与异步·第二话——Promise:一诺千金
一.难以掌控的回调 我在第一话中介绍了异步的概念.事件循环.以及JS编程中可能的3种异步情况(用户交互.I/O.定时器).在编写异步操作代码时,最直接.也是每个JSer最先接触的写法一定是回调函数(c ...