state thread

 

State Thread 的官網地址:http://state-threads.sourceforge.nethtml

The State Threads Library is a small application library which provides a foundation for writing fast and highly scalable Internet applications (such as web servers, proxy servers, mail transfer agents, and so on, really any network-data-driven application) on UNIX-like platforms. It combines the simplicity of the multithreaded programming paradigm, in which one thread supports each simultaneous connection, with the performance and scalability of an event-driven state machine architecture. In other words, this library offers a threading API for structuring an Internet application as a state machine.web

傳統的服務器端開發模型是:採用session異步方式調用來(調用其它好多服務時須要存狀態,來完成一個完整的流程調用),這種方式稍稍複雜一點。採用」協程」的好處,經常被人提起的是:迴歸「同步調用型」的開發模型:程序調用api1(),api1返回以後調api2(),等api2返回以後調api3.. and so on. 這樣的開發模式更加容易理解。api

1、setjmp/longjmp

ST中使用了setjmp、longjmp來實現協程,內容參考另外一篇博文:http://www.cnblogs.com/ym65536/p/4984339.html數組

2、state threads

本文的分析主要基於state threads實現進行分析。爲求簡單,忽略掉一些平臺強相關的邏輯。服務器

2.1 事件系統(_st_eventsys_t)

常見的方式:poll/select/epoll/kqueue等等(默認採用select)。實現方式和其它的一些事件系統(ae/libevent)有點類型,因此此文再也不詳細地作剖析。session

 1 typedef struct _st_eventsys_ops {
 2  const char *    name;     /* Name of this event system */
 3  int    val;               /* Type of this event system */
 4  int    (*init)(void);     /* Initialization */
 5  void   (*dispatch)(void); /* Dispatch function */
 6  int    (*pollset_add)(struct pollfd *, int);     /* Add descriptor set */
 7  void   (*pollset_del)(struct pollfd *, int);    /* Delete descriptor set */
 8  int    (*fd_new)(int);       /* New descriptor allocated */
 9  int    (*fd_close)(int);     /* Descriptor closed */
10  int    (*fd_getlimit)(void); /* Descriptor hard limit */
11 } _st_eventsys_t;

 

2.2 協程態(_st_vp_t)

每一個vp對應一個idle協程,4個隊列,切換回調等。app

 1 typedef struct _st_vp {
 2   _st_thread_t *idle_thread;  /* Idle thread for this vp */
 3   st_utime_t last_clock;      /* The last time we went into vp_check_clock() */
 4 
 5   _st_clist_t run_q;          /* run queue for this vp */
 6   _st_clist_t io_q;           /* io queue for this vp */
 7   _st_clist_t zombie_q;       /* zombie queue for this vp */
 8 #ifdef DEBUG
 9   _st_clist_t thread_q;       /* all threads of this vp */
10 #endif
11   int pagesize;
12 
13   _st_thread_t *sleep_q;      /* sleep queue for this vp */
14   int sleepq_size;          /* number of threads on sleep queue */
15 
16 #ifdef ST_SWITCH_CB
17   st_switch_cb_t switch_out_cb;    /* called when a thread is switched out */
18   st_switch_cb_t switch_in_cb;    /* called when a thread is switched in */
19 #endif
20 } _st_vp_t;

 

2.3 隊列(run/io/zombie/sleep queue)

st使用了4個隊列, 這個在上面的結構中就已經有,爲了訪問的方便,定義了4組宏(debug模式下,還有另外一個),以下。這4個隊列分別對應了4種狀態,由此造成本身的狀態機體系。異步

每當建立一個微線程時,都會將其加入到RUNQ中。ide

1 #define _ST_RUNQ    (_st_this_vp.run_q)
2 #define _ST_IOQ     (_st_this_vp.io_q)
3 #define _ST_ZOMBIEQ (_st_this_vp.zombie_q)
4 #ifdef DEBUG
5 #define _ST_THREADQ (_st_this_vp.thread_q)
6 #endif
7 #define _ST_SLEEPQ  (_st_this_vp.sleep_q)

 

2.4 微線程(_st_thread_t)

在vp中,咱們看到有一個成員:idle_thread. 天然會有疑問:這貨是幹嗎的?這貨長的像個線程,但其實固然不是線程,就以「微線程」來稱吧:即用戶態下實現的線程。wordpress

主要關注點:

  1. 微線程的狀態機
  2. 每一個微線程維護的棧空間、私有數據
  3. 每一個微線程的入口點
  4. 咱們熟悉的jmp_buf上下文:context
 1 typedef struct _st_thread _st_thread_t;
 2  
 3 struct _st_thread {
 4  int     state; /* Thread's state */
 5  int     flags; /* Thread's flags */
 6  
 7  void *   (*start)(void *arg); /* The start function of the thread */
 8  void *   arg;                 /* Argument of the start function */
 9  void *   retval;             /* Return value of the start function */
10  
11 _st_stack_t *   stack;       /* Info about thread's stack */
12  
13 _st_clist_t     links;       /* For putting on run/sleep/zombie queue */
14  _st_clist_t    wait_links;  /* For putting on mutex/condvar wait queue */
15 #ifdef DEBUG
16  _st_clist_t    tlink;       /* For putting on thread queue */
17 #endif
18  
19  st_utime_t     due;      /* Wakeup time when thread is sleeping */
20  _st_thread_t * left;     /* For putting in timeout heap */
21  _st_thread_t * right;    /* -- see docs/timeout_heap.txt for details */
22  int            heap_index;
23  
24 void **         private_data; /* Per thread private data */
25  
26 _st_cond_t *    term;       /* Termination condition variable for join */
27  
28 jmp_buf         context;    /* Thread's context */
29 };

 

2.5 棧(_st_stack_t)

每一個微線程自身單獨都會維護本身的一個「棧空間」。棧有本身獨立的內存,大小,棧底,棧頂,棧指針(程序級別、非系統內存級別),恢復點。這裏的「棧」只是一個概念(實際是堆,經過malloc或mmap來實現),並不是咱們一般指的棧。

typedef struct _st_stack {
_st_clist_t     links;
char *          vaddr;              /* Base of stack's allocated memory */
int             vaddr_size;         /* Size of stack's allocated memory */
int             stk_size;           /* Size of usable portion of the stack */
char *          stk_bottom;         /* Lowest address of stack's usable portion */
char *          stk_top;            /* Highest address of stack's usable portion */
void *          sp;                 /* Stack pointer from C's point of view */
#ifdef __ia64__
void *          bsp;                /* Register stack backing store pointer */
#endif
} _st_stack_t;

 

_st_stack的可用空間大小缺省爲:128k,即剛開始分配時的大小。當不夠用時,會增長一個內存頁的大小(getpagesize),固然的大小必須是內存頁的整數倍。(詳見st_thread_create)。這部分是可用棧空間的大小,實際大小還會加上:2*REDZONE+extra。 在stack的每一端(棧頂、棧底)都有一個REDZONE,其大小也是一個分頁;而extra則看是否須要(0或者一個分頁大小)

在st_stack中,入棧的方式,有兩種:向下增加,向上增長。以向上增加爲例(向下增加的話,相似,反之便可):

2.6 jmp_buf上下文

即然棧是本身實現的,那邊對應的jmp_buf裏頭的sp、bsp也須要跟着變。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
_ST_INIT_CONTEXT( thread , stack->sp, stack->bsp, _st_thread_main);
...
#define MD_SETJMP(env)           _setjmp(env)
#define MD_LONGJMP(env, val)     _longjmp(env, val)
...
#define MD_INIT_CONTEXT(_thread, _sp, _bsp, _main) \
ST_BEGIN_MACRO \
if (MD_SETJMP((_thread)->context)) \
     _main(); \
memcpy (( char *)(_bsp) - MD_STACK_PAD_SIZE, \
         ( char *)(_thread)->context[0].__jmpbuf[17] - MD_STACK_PAD_SIZE, \
         MD_STACK_PAD_SIZE); \
(_thread)->context[0].__jmpbuf[0] = ( long ) (_sp); \
(_thread)->context[0].__jmpbuf[17] = ( long ) (_bsp); \
ST_END_MACRO

3 微線程的建立

3.1 總體流程

微線程的創立,經過_st_thread_create()來完成。

主要過程以下:

  1. 建立棧,分配空間。
  2. 初始化sp/bsp、私有數據(private_data)以及微線程自身所需空間(thread),入口函數(start)及參數。
  3. 調用setjmp,若成功,則執行_st_thread_main();
  4. 將以前的sp恢復.

其中的第三步,_st_thread_main,也就是微線程自身的執行點,過程也很簡單:

  1. 獲取當前微線程
  2. 將微線程對應的棧空間位置狀態變量置爲0(state和flags,轉型爲valatile,防止longjmp切換時更改值。ps: 這裏有個關於二級指針數組[1]的tricks)
  3. 執行微線程的入口函數(start)
  4. 退出微線程(_st_thread_exit)

退出微線程時,會發生一些很奇妙的事情:

  1. 清理工做
  2. 對term作檢查,是否已退出。如果,添加thread到殭屍隊列:zombieq中, 通知term信號,並切換context(_ST_SWITCH_CONTEXT,清理term
  3. 清理stack(_st_stack_free)
  4. 切換至另外一個thread執行(_ST_SWITCH_CONTEXT)
上面就是一個總體的流程。而其中有兩個地方,須要特別再關注一下。

3.2 上下文切換

由於涉及到微線程的切換,即然是「切換」,那麼這裏確定至少涉及到兩個微線程。因爲微線程自己處在RUNQ中,切換的話,天然會將next。

  1. 若是runq隊列有在運行,切換至next; 若是沒有,切換至idle_thread
  2. 將切換後的當前微線程置爲run狀態
  3. longjmp置相應微線程的jmp_buf處,恢復棧執行
 

4.小結

state thread的代碼很簡潔,若是知道原理的話,分析起來不困難。另外,它還簡單地實現了一個http server。有興趣的朋友能夠作爲參照本身實現功能更復雜的http server。
相關文章
相關標籤/搜索