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
ST中使用了setjmp、longjmp來實現協程,內容參考另外一篇博文:http://www.cnblogs.com/ym65536/p/4984339.html數組
本文的分析主要基於state threads實現進行分析。爲求簡單,忽略掉一些平臺強相關的邏輯。服務器
常見的方式: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;
每一個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;
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)
在vp中,咱們看到有一個成員:idle_thread. 天然會有疑問:這貨是幹嗎的?這貨長的像個線程,但其實固然不是線程,就以「微線程」來稱吧:即用戶態下實現的線程。wordpress
主要關注點:
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 };
每一個微線程自身單獨都會維護本身的一個「棧空間」。棧有本身獨立的內存,大小,棧底,棧頂,棧指針(程序級別、非系統內存級別),恢復點。這裏的「棧」只是一個概念(實際是堆,經過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中,入棧的方式,有兩種:向下增加,向上增長。以向上增加爲例(向下增加的話,相似,反之便可):
即然棧是本身實現的,那邊對應的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
|
微線程的創立,經過_st_thread_create()來完成。
主要過程以下:
其中的第三步,_st_thread_main,也就是微線程自身的執行點,過程也很簡單:
退出微線程時,會發生一些很奇妙的事情:
由於涉及到微線程的切換,即然是「切換」,那麼這裏確定至少涉及到兩個微線程。因爲微線程自己處在RUNQ中,切換的話,天然會將next。