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

这里有一个基本的协程例子 http://www.csl.mtu.edu/cs4411.ck/www/NOTES/non-local-goto/coroutine.html, 可以了解setjmp和longjmp的基本用法。如还有不懂,请自行查阅其他资料。本文主要关注st基于setjmp和longjmp的实现原理及其程序结构。

st基本介绍 http://state-threads.sourceforge.net/docs/st.html。

从中可以看出,IA(Internet Application)架构演化历史:

1.多进程MP

以Apache为代表的web server。创建进程服务新用户,开销过高。调度单位是进程。

2.多线程MT

创建新线程服务新用户,线程间上下文切换,锁竞争等等,增加了额外开销。调度单位是线程。

3.事件驱动状态机EDSM

基于IO复用机制,实现大量并发请求的处理。但程序是一体的,并不是基于线程,新程序需要从头开始。该模式下主要使用回调和状态参数来进行上下文切换,实际上是以一种非常艰难和痛苦的方式实现了类似线程和栈的思想。ESDM最大的问题是其“将线性思路分解成大量的回调所固有的复杂性”,导致程序难以实现,扩展和维护。

传统EDSM程序架构:

4.协程

调度单位减小到函数,上下文切换不需要内核参与,不存在系统调用。上下文切换开销降到最低,系统调用降到最低,没有锁竞争,没有信号处理。保留了程序对请求的线性处理逻辑,提高了程序的开发效率,可扩展性和可维护性。

基于st的ESDM程序模型:

基于setjmp和longjmp实现协程库基本步骤(下述线程指用户线程):

1.需要用jmpbuf变量保存每一个线程的运行时环境,称为线程上下文context。

2.为每个线程分配(malloc/mmap)一个stack,用于该线程运行时栈,该stack完全等效于普通系统线程的函数调用栈。该stack地址是在线程初始化时设置,所以不需要考虑setjmp时保存线程的栈上frames数据的问题。

3.通过调用setjmp初始化线程运行时上下文,将context数据存放到jmpbuf结构中。然后修改其中的栈指针sp指向上一步分配的stack。根据当前系统栈的增长方向,将sp设置为stack的最低或最高地址。

4.线程退出时,需要返回到一个安全的系统位置。即,需要有一个主线程main thread或idle thread来作为其他线程最终的退出跳转地址。需要为主线程保存一个jmpbuf。

5.设置过main thread的jmpbuf后,需要跳转到其他线程开始执行业务线程。

6.实现一个context交换函数,在多个线程之间进行跳转:保存自己的jmpbuf,longjmp到另一个线程的jmpbuf。

st基于setjmp和longjmp的具体实现:

线程初始化:

#define MD_INIT_CONTEXT(_thread, _sp, _main) \ST_BEGIN_MACRO                             \if (MD_SETJMP((_thread)->context))         \_main();                                 \MD_GET_SP(_thread) = (long) (_sp);         \ST_END_MACRO

很明显可以看到,setjmp(将jmpbuf存放到thread->context)之后,同时修改它的栈指针sp指向新分配的线程stack->sp地址。该sp指针用于该thread以后的栈frames数据存储。

线程切换:

#define _ST_SWITCH_CONTEXT(_thread)       \ST_BEGIN_MACRO                        \ST_SWITCH_OUT_CB(_thread);            \if (!MD_SETJMP((_thread)->context)) { \_st_vp_schedule();                  \}                                     \ST_DEBUG_ITERATE_THREADS();           \ST_SWITCH_IN_CB(_thread);             \ST_END_MACRO

其中主要时MD_SETJMP保存当前context,然后调用_st_vp_schedule()从_ST_RUNQ上取第一个可运行的thread,并调用_ST_RESTORE_CONTEXT将该thread恢复运行。

线程恢复:

#define _ST_RESTORE_CONTEXT(_thread)   \ST_BEGIN_MACRO                     \_ST_SET_CURRENT_THREAD(_thread);   \MD_LONGJMP((_thread)->context, 1); \ST_END_MACRO

起始线程primordial thread和休眠线程idle thread:

/** Initialize this Virtual Processor*/
int st_init(void)
{_st_thread_t *thread;if (_st_active_count) {/* Already initialized */return 0;}/* We can ignore return value here */st_set_eventsys(ST_EVENTSYS_DEFAULT);if (_st_io_init() < 0)return -1;memset(&_st_this_vp, 0, sizeof(_st_vp_t));ST_INIT_CLIST(&_ST_RUNQ);ST_INIT_CLIST(&_ST_IOQ);ST_INIT_CLIST(&_ST_ZOMBIEQ);
#ifdef DEBUGST_INIT_CLIST(&_ST_THREADQ);
#endifif ((*_st_eventsys->init)() < 0)return -1;_st_this_vp.pagesize = getpagesize();_st_this_vp.last_clock = st_utime();/** Create idle thread*/_st_this_vp.idle_thread = st_thread_create(_st_idle_thread_start,NULL, 0, 0);if (!_st_this_vp.idle_thread)return -1;_st_this_vp.idle_thread->flags = _ST_FL_IDLE_THREAD;_st_active_count--;_ST_DEL_RUNQ(_st_this_vp.idle_thread);/** Initialize primordial thread*/thread = (_st_thread_t *) calloc(1, sizeof(_st_thread_t) +(ST_KEYS_MAX * sizeof(void *)));if (!thread)return -1;thread->private_data = (void **) (thread + 1);thread->state = _ST_ST_RUNNING;thread->flags = _ST_FL_PRIMORDIAL;_ST_SET_CURRENT_THREAD(thread);_st_active_count++;
#ifdef DEBUG_ST_ADD_THREADQ(thread);
#endifreturn 0;
}

在st_init里面,创建primordial thread和idle thread。primordial thread作为起始线程当有其他线程加入运行队列后从该线程切出,idle thread作为背景线程在没有可运行线程的时候执行io调度函数分发事件。

st_init里面调用st_thread_create并不会开始执行idle线程,创建其他线程也一样,只有在直接或间接调用_st_vp_schedule之后才会开始执行RUNQ上面的线程。_st_vp_schedule函数在_ST_SWITCH_CONTEXT中被调用。

st程序结构:

st底层基于event-driven select/poll/kqueue/epoll等IO复用机制。下面以epoll为例说明st底层事件管理机制。

st中有IOQ,ZOMBIEQ,RUNQ,SLEEPQ等几个队列,用来存储处于对应状态的threads。

  • RUNQ中存储的是可以被调度运行的threads,每次调用_st_vp_schedule即从该队列取出一个thread去运行。
  • IOQ存储处于IO等待状态的threads,当上层调用st_poll时,将该thread放入IOQ中;当底层epoll有IO事件到达时,将该thread从IOQ中移除,并放入RUNQ中。
  • 当thread退出时,放入ZOMBIEQ中。
  • 当st_poll传入超时参数>0或调用st_usleep和st_cond_timewait时,将thread加入SLEEPQ中。

协程库st(state threads library)原理解析相关推荐

  1. C++ 开源协程库 libco——原理及应用

    1 导论 使用 C++ 来编写高性能的网络服务器程序,从来都不是件很容易的事情.在没有应用任何网络框架,从 epoll/kqueue 直接码起的时候尤其如此.即便使用 libevent, libev这 ...

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

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

  3. 微信 libco 协程库原理剖析

    作者:alexzmzheng 同 Go 语言一样,libco 也是提供了同步风格编程模式,同时还能保证系统的高并发能力,本文主要剖析 libco 中的协程原理. 简介 libco 是微信后台大规模使用 ...

  4. 在C语言中实现协程库(一)----------协程切换原理详解

    从这篇文章开始,我将一点一点详细介绍如何在c语言中实现协程库.并对其中涉及到的技术进行详细的解释. 感兴趣的小伙伴欢迎一起参与 代码地址 协程切换原理 使用glibc中<ucontext.h&g ...

  5. ucontext-人人都可以实现的简单协程库

    1.干货写在前面 协程是一种用户态的轻量级线程.本篇主要研究协程的C/C++的实现. 首先我们可以看看有哪些语言已经具备协程语义: 比较重量级的有C#.erlang.golang* 轻量级有pytho ...

  6. Kotlin协程:挂起与恢复原理逆向刨析

    前言:只有在那崎岖的小路上不畏艰险奋勇攀登的人,才有希望达到光辉的顶点. --马克思 前言 经过前面两篇协程的学习,我相信大家对协程的使用已经非常熟悉了.本着知其然更要知其之所以然的心态,很想知道它里 ...

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

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

  8. C/C++协程库libco:微信怎样漂亮地完成异步化改造

    如今,微信拥有月活跃用户8亿. 不可否认,当今的微信后台拥有着强大的并发能力. 不过, 正如罗马并非一日建成:微信的技术也曾经略显稚嫩. 微信诞生于2011年1月,当年用户规模为0.1亿左右:2013 ...

  9. libco协程库源码解读

    2019独角兽企业重金招聘Python工程师标准>>> 协程,又被称为用户级线程,是在应用层被调度,可以减少因为调用系统调用而阻塞的线程切换的时间.目前有很多协程的实现,由于微信内部 ...

最新文章

  1. (转载)人人都会OSGI--实例讲解OSGI开发
  2. 论5级流水32bit risc cpu设计
  3. HTTPS协议原理分析
  4. 计算机办公应用高级教案,办公自动化高级应用电子教案.pdf
  5. 容器编排技术 -- 创建Kubernetes集群
  6. 如何给影像添加投影_地面互动投影是如何实现的,需要哪些设备?
  7. Web UI设计师的CSS优化工具 25+
  8. idea打包 jar文件
  9. GATK-HaplotypeCaller 变异检测详解
  10. 聆秘2013手游渠道之争:四大战役谁主沉浮?
  11. python爬虫脚本 初级入门爬虫英雄联盟所有皮肤_用Python爬取英雄联盟(lol)全部皮肤...
  12. 固定资产减值准备、累计折旧
  13. Verilog流水线CPU设计(超详细)
  14. Android的MVVM架构的单Activity应用实践
  15. 程序员兼职接私活平台大全,兼职也能月薪上万!
  16. c语言sscanf函数和结构体,C语言sprintf与sscanf函数 -电脑资料
  17. 基于Modis的遥感数据的地表温度的获取解决方案--以京津唐为例
  18. 反欺诈类优秀文章汇总
  19. 一个关于SDWAN单臂部署方案验证的实验
  20. jstl json数据 ajax,JSTL,JQuery,Ajax,Json

热门文章

  1. IBM DS4800控制器更换和电池误告警故障处理方案
  2. Async下处理多个异常
  3. Silverlight BUG
  4. SQL Server 2005两本教程案例数据库
  5. TCP/IP协议是什么
  6. CodeForces - 932G Palindrome Partition(回文自动机+Palindrome Series优化dp)
  7. 牛客 - 点对最大值(树的直径)
  8. java数据同步处理_Java如何处理多线程的数据同步问题
  9. QT乱码总结3.UNICODE有无BOM
  10. Adobe Media Server 5(AMS)的安装及使用