zoukankan      html  css  js  c++  java
  • 协程库st(state threads library)原理解析

    协程库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 DEBUG
      ST_INIT_CLIST(&_ST_THREADQ);
    #endif
    
      if ((*_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);
    #endif
    
      return 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中。
  • 相关阅读:
    部署 AppGlobalResources 到 SharePoint 2010
    还原一个已删除的网站集
    使用仪表板设计器配置级联筛选器 (SharePoint Server 2010 SP1)
    File or arguments not valid for site template
    Pex and Moles Documentation
    Content Query Webpart匿名访问
    Running Moles using NUnit Console from Visual Studio
    Calling a WCF Service using jQuery in SharePoint the correct way
    Updating Content Types and Site Columns That Were Deployed as a Feature
    asp.net中判断传过来的字符串不为空的代码
  • 原文地址:https://www.cnblogs.com/lidabo/p/14439617.html
Copyright © 2011-2022 走看看