什么是協(xié)程
協(xié)程是計(jì)算機(jī)程序組件,它通過允許多個(gè)入口點(diǎn)在某些位置暫停和恢復(fù)執(zhí)行來(lái)概括用于非搶占式多任務(wù)的子程序。協(xié)程的思想本質(zhì)上就是控制流的主動(dòng)讓出和恢復(fù)機(jī)制。
協(xié)程(coroutine)和子程序(subroutines)的區(qū)別
子程序是協(xié)程的特殊情況。當(dāng)子程序被調(diào)用,則開始執(zhí)行宇挫,當(dāng)子程序結(jié)束,則停止執(zhí)行匾乓。子程序只返回一次捞稿,在調(diào)用之間不保持狀態(tài)。協(xié)程A可以通過調(diào)用協(xié)程B退出執(zhí)行拼缝,并且后續(xù)可以恢復(fù)協(xié)程A的執(zhí)行娱局,即協(xié)程實(shí)例保持狀態(tài),并且在調(diào)用之間變化咧七。
協(xié)程(coroutine)和生成器(generators)的區(qū)別
生成器主要用于簡(jiǎn)化迭代器的寫入衰齐,生成器中的yield語(yǔ)句不指定要跳轉(zhuǎn)到的協(xié)程,而是將值傳遞回父例程继阻。
協(xié)程的應(yīng)用
-
Communicating sequential processes where each sub-process is a coroutine. Channel inputs/outputs and blocking operations yield coroutines and a scheduler unblocks them on completion events.
TODO(caikun): 補(bǔ)充其他的示例
什么時(shí)候使用協(xié)程
TODO
協(xié)程示例
var q := new queue
coroutine produce
loop
while q is not full
create some new items
add the items to q
yield to consume
coroutine consume
loop
while q is not empty
remove some items from q
use the items
yield to produce
協(xié)程的實(shí)現(xiàn)
Linux系統(tǒng)對(duì)用戶上下文管理以及切換的支持
man getcontext
getcontext, setcontext - get or set the user context
int getcontext(ucontext_t *ucp);
int setcontext(const ucontext_t *ucp);
getcontext將當(dāng)前活動(dòng)上下文信息存入ucp
setcontext恢復(fù)ucp指向的上下文信息
示例
#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>
int main(int argc, const char *argv[]){
ucontext_t context;
getcontext(&context);
puts("Hello world");
sleep(1);
setcontext(&context);
return 0;
}
// output are following
Hello world
Hello world
... (repeated every second)
解釋:
- getcontext將當(dāng)前活躍上下文存入context
- 輸出Hello world耻涛,并睡眠一秒
- setcontext恢復(fù)context,即從新回到puts("Hello world")語(yǔ)句
- 一直重復(fù)2, 3步瘟檩,永不退出
man makecontext
makecontext, swapcontext - manipulate user context
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
makecontext會(huì)修改ucp指向的上下文信息(ucp是通過getcontext獲取的上下文信息)抹缕。在調(diào)用makecontext之前,需要設(shè)置好ucp->uc_stack墨辛,ucp->uc_link等信息卓研。ucp->uc_stack指向的棧空間,用于新的用戶上下文存儲(chǔ)寄存器奏赘,臨時(shí)變量等寥闪;當(dāng)ucp指向的上下文執(zhí)行結(jié)束時(shí),即調(diào)度到ucp->uc_link指向的context磨淌。如果該context為NULL疲憋,則進(jìn)程結(jié)束。
swapcontext保存當(dāng)前的上下文到oucp梁只,并切換到ucp指向的上下文缚柳。
示例
#include <ucontext.h>
#include <stdio.h>
#include <stdlib.h>
static ucontext_t uctx_main, uctx_func1, uctx_func2;
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
static void
func1(void)
{
printf("func1: started\n");
printf("func1: swapcontext(&uctx_func1, &uctx_func2)\n");
if (swapcontext(&uctx_func1, &uctx_func2) == -1)
handle_error("swapcontext");
printf("func1: returning\n");
}
static void
func2(void)
{
printf("func2: started\n");
printf("func2: swapcontext(&uctx_func2, &uctx_func1)\n");
if (swapcontext(&uctx_func2, &uctx_func1) == -1)
handle_error("swapcontext");
printf("func2: returning\n");
}
int
main(int argc, char *argv[])
{
char func1_stack[16384];
char func2_stack[16384];
if (getcontext(&uctx_func1) == -1)
handle_error("getcontext");
uctx_func1.uc_stack.ss_sp = func1_stack;
uctx_func1.uc_stack.ss_size = sizeof(func1_stack);
uctx_func1.uc_link = &uctx_main;
makecontext(&uctx_func1, func1, 0);
if (getcontext(&uctx_func2) == -1)
handle_error("getcontext");
uctx_func2.uc_stack.ss_sp = func2_stack;
uctx_func2.uc_stack.ss_size = sizeof(func2_stack);
/* Successor context is f1(), unless argc > 1 */
uctx_func2.uc_link = (argc > 1) ? NULL : &uctx_func1;
makecontext(&uctx_func2, func2, 0);
printf("main: swapcontext(&uctx_main, &uctx_func2)\n");
if (swapcontext(&uctx_main, &uctx_func2) == -1)
handle_error("swapcontext");
printf("main: exiting\n");
exit(EXIT_SUCCESS);
}
// output are following
./test
main: swapcontext(&uctx_main, &uctx_func2)
func2: started
func2: swapcontext(&uctx_func2, &uctx_func1)
func1: started
func1: swapcontext(&uctx_func1, &uctx_func2)
func2: returning
func1: returning
main: exiting
./test 1
main: swapcontext(&uctx_main, &uctx_func2)
func2: started
func2: swapcontext(&uctx_func2, &uctx_func1)
func1: started
func1: swapcontext(&uctx_func1, &uctx_func2)
func2: returning
解釋:
- uctx_func1用的棧空間為func1_stack敛纲,并且后繼上下文為uctx_main
- uctx_func2用的椢够鳎空間為func2_stack,并且后繼上下文為uctx_func1或者NULL
- 輸出 main: swapcontext(&uctx_main, &uctx_func2)
- swapcontext切換到uctx_func2指向的上下文淤翔,并將當(dāng)前的上下文保存在uctx_main
- 輸出func2: started
- 輸出func2: swapcontext(&uctx_func2, &uctx_func1)
- swapcontext切換到uctx_func2指向的上下文
- 輸出func1: started
- 輸出func1: swapcontext(&uctx_func1, &uctx_func2)
- 切換回uctx_func2指向的上下文
- 輸出 func2: returning
- 運(yùn)行 uctx_func2.uc_link指向的context
- 若uctx_func2.uc_link == NULL, 則程序退出
- 若 uctx_func2.uc_link != NULL佩谷, 即在程序中其指向uctx_func1旁壮,則恢復(fù)指向uctx_func1指向的上下文
- 輸出 func1: returning
- 執(zhí)行uctx_func1.uc_link指向的上下文,即uctx_main
- 輸出 main: exiting谐檀,程序退出
注:在這個(gè)示例程序中抡谐,uc_stack是context用到的棧空間桐猬,設(shè)置為16KB麦撵;線程最小stack空間定義在pthread.h中,由常量PTHREAD_STACK_MIN指定溃肪,Debian GNU/Linux 8上為#define PTHREAD_STACK_MIN 16384.