写个百万级别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 ...
随机推荐
- [Linux] PHP程序员玩转Linux系列-telnet轻松使用邮箱
1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转L ...
- Python 一行代码
Python语法十分便捷,通过几个简单例子了解其趣味 1.Fizz.Buzz问题为: 打印数字1到100, 3的倍数打印"Fizz", 5的倍数打印"Buzz" ...
- AngularJS创建新指令directive参数说明
var myapp = angular.module('myapp', []); myapp.directive('worldname', function() { return { template ...
- CF #344 D. Messenger KMP/Z
题目链接:http://codeforces.com/problemset/problem/631/D 给定两个压缩形式的字符串,如a3b5a4k7这样的形式 问A在B中出现次数. 分类讨论,如果A是 ...
- pt-online-schema-change的原理解析与应用说明
PERCONA提供了若干管理维护MySQL的小工具,集成在 PERCONA Toolkit工具中,有慢查询分析.主从差异对比.主从差异修复及在线表结构修改等工具,个人觉得挺好用的.本文简单 ...
- Let's Encrypt: 为CentOS/RHEL 7下的nginx安装https支持-具体案例
环境说明: centos 7 nginx 1.10.2 前期准备 软件安装 yum install -y epel-release yum install -y certbot 创建目录及链接 方法1 ...
- xmlplus 组件设计系列之一 - 图标
网页上使用的图标分可为三种:文件图标.字体图标和 SVG 图标.对于文件图标,下面仅以 PNG 格式来说明. PNG 图标 对于 PNG 图标的引用,有两种方式.一种是直接由 HTML 元素 img ...
- 深入浅出学习HTTP协议
之前学习javaWeb只是大致了解了一下,今天重点介绍下http请求,当是复习吧! 一.http基础概念 1.什么是http协议? HTTP是Hyper Text Transfer Protocol( ...
- poj2155二维树状数组
Given an N*N matrix A, whose elements are either 0 or 1. A[i, j] means the number in the i-th row an ...
- kafka java使用
首先添加maven依赖 Kafka <dependency> <groupId>org.apache.kafka</groupId> <artifactId& ...