协程库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, ); \
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 ;
} /* We can ignore return value here */
st_set_eventsys(ST_EVENTSYS_DEFAULT); if (_st_io_init() < )
return -; memset(&_st_this_vp, , sizeof(_st_vp_t)); ST_INIT_CLIST(&_ST_RUNQ);
ST_INIT_CLIST(&_ST_IOQ);
ST_INIT_CLIST(&_ST_ZOMBIEQ);
#ifdef DEBUG
ST_INIT_CLIST(&_ST_THREADQ);
#endif if ((*_st_eventsys->init)() < )
return -; _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, , );
if (!_st_this_vp.idle_thread)
return -;
_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(, sizeof(_st_thread_t) +
(ST_KEYS_MAX * sizeof(void *)));
if (!thread)
return -;
thread->private_data = (void **) (thread + );
thread->state = _ST_ST_RUNNING;
thread->flags = _ST_FL_PRIMORDIAL;
_ST_SET_CURRENT_THREAD(thread);
_st_active_count++;
#ifdef DEBUG
_ST_ADD_THREADQ(thread);
#endif return ;
}

在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. libco协程库上下文切换原理详解

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

  2. 写个百万级别full-stack小型协程库——原理介绍

    其实说什么百万千万级别都是虚的,下面给出实现原理和测试结果,原理很简单,我就不上图了: 原理:为了简单明了,只支持单线程,每个协程共享一个4K的空间(你可以用堆,用匿名内存映射或者直接开个数组也都是可 ...

  3. 转:一个C语言实现的类似协程库(StateThreads)

    http://blog.csdn.net/win_lin/article/details/8242653 译文在后面. State Threads for Internet Applications ...

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

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

  5. C高级 跨平台协程库

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

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

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

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

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

  8. libaco: 一个极速的轻量级 C 非对称协程库 🚀 (10 ns/ctxsw + 一千万协程并发仅耗内存 2.8GB + Github Trending)

    0 Name 简介 libaco - 一个极速的.轻量级.C语言非对称协程库. 这个项目的代号是Arkenstone 

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

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

随机推荐

  1. scanf()与gets()的区别

    scanf()与gets()的区别 1.scanf()可以同时接受多个字符串,而gets()一次只能接受一个字符串. #include<stdio.h>int main(){ char s ...

  2. centos7 开放mongodb端口

    CentOS 7 默认没有使用iptables,所以通过编辑iptables的配置文件来开启端口是不可以的 CentOS 7 采用了 firewalld 防火墙 如要查询是否开启27019端口则: 1 ...

  3. hasura graphql 角色访问控制

    目前从官方文档以及测试可以看出不加任何header的请求访问的是所有的数据,对于具有访问 控制的请求需要添加请求头,实际生产的使用需要集合web hook 的实现访问控制. 参考配置 访问请求 目前数 ...

  4. Kafka问题排查(消费者自动关闭)

    问题描述:            在消费端能够正常消费到Kafka数据并成功生产到producer topic 中,当将kafka的一台机器关机之后,正常情况下应该是 消费端是不受影响的.因为有还有两 ...

  5. excel 条件格式 心的

    例1: 图1 图2 $G$16 ≠G16  用G16就可以用格式刷拖动,$G$16用格式刷刷到其它单元格保持不变,判断单元格函数 ISBLANK(G16)=TRUE

  6. HTML第三讲(选择符)

    本次课程讲CSS中的选择符 1.基本选择符 基本选择符有三个 1.标记名选择符 所谓的标记名选择符就是直接在样式中使用标记名定义,譬如以下代码: (此种选择符的特点是所有相同的标记名可以同时定义不需要 ...

  7. A Newbie’s Install of Keras & Tensorflow on Windows 10 with R

    This weekend, I decided it was time: I was going to update my Python environment and get Keras and T ...

  8. Android Studio使用JDBC远程连接mysql的注意事项(附示例)

    JDBC为java程序访问各种类型的关系型数据库提供了统一的接口,用户不必针对不同数据库写出不同的代码,但是使用JDBC必须得下载相应的驱动,比如我这里是要连接mysql,于是就到mysql官网去下载 ...

  9. Oracle 11g 新特性 -- Oracle Restart 说明(转载)

    转载:http://blog.csdn.net/tianlesoftware/article/details/8435670 一.  OHASD 说明 Oracle 的Restart 特性是Oracl ...

  10. 为Web页中的Table对象创建一个映射表

    HTML对象中的TABLE是我们常用的网页元素,在DHTML编程中,我们可以通过它的rows和cells方法方便的访问表格对象里面的每一个单元格,而且表格对象(table)的每个单元行(tr)和每个单 ...