原始碼在這里
對于作業系統而言,行程是最小的資源管理單元,執行緒是最小的執行單元,對于一個執行緒,在使用中,性能可能會受到以下因素的影響:
- 涉及到執行緒阻塞狀態和可運行狀態之間的切換
- 執行緒背景關系的切換
- 同步鎖
- ……
所以引入協程——更加輕量級的執行緒,
就像行程和執行緒的關系一樣,一個行程可以擁有多個執行緒,一個執行緒可以擁有多個協程,
協程是在用戶態執行的,所以不會像執行緒切換那樣消耗資源,使性能得到提升,可以說,執行緒是一個特殊的函式,這個函式可以在某個地方掛起,并且可以在掛起處繼續運行,一個執行緒內可以由多個這樣的特殊的函式在運行,但是一個執行緒的多個協程的運行是串行的,如果是多核CPU,多個行程或一個行程內的多個執行緒是可以并行運行的,但是一個執行緒內協程卻絕對是串行的,畢竟協程仍然是一個函式,一個執行緒內可以運行多個函式,但是每個函式都是串行運行的,當一個協程運行時,其他的協程必須被掛起,
C語言背景關系切換的庫ucontext:
typedef struct ucontex {
stack_t uc_stack; // 當前背景關系要用到的堆疊
mcontext_t uc_mctx; // 跟硬體相關的資訊
struct ucontext *uc_link; //保存當前context結束后繼續執行的context記錄
sigset_t uc_sigmask; // 存的背景關系的運行期間要屏蔽的信號集合
}ucontext_t;
// stack_t結構體
typedef struct {
void *ss_sp; // 堆疊的起始位置
int ss_size; // 堆疊大小
int ss_flags; // 標志
}stack_t;
- int getcontext(ucontext_t *ucp); 獲取當前背景關系
- int setcontext(const ucontext_t *ucp); 將當前程式背景關系置為ucp指向的背景關系
- void makecontext(ucontext_t *ucp, void (*func)(), int argc, …);修改ucp指向的背景關系, 背景關系轉而指向函式func, argc是傳入引數個數, …為傳入引數(直接就是引數, 不用指標),也就是構造背景關系,
- int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);保存當前背景關系, 切換到新的背景關系, 等于是先執行getcontext(oucp) 再執行setcontext(ucp)
寫個程式感受一波 getcontext() 和 setcontext()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>
int main() {
int i;
ucontext_t ctx;
getcontext(&ctx);
printf("i = %d\n",i++);
sleep(1);
setcontext(&ctx);
}
先解釋一波:首先 getcontext() 獲取背景關系,然后往下邊執行,執行到 setcontext() 就會去執行getcontext() 獲取到的背景關系,也就是說就會回到 getcontext() 所在位置繼續往下執行,從而形成一個回圈,回圈的列印 i 的值,結果如下:

再來個程式感受一波 makecontext() :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>
void fun() {
printf("fun()\n");
}
int main() {
int i = 1;
char *stack = (char*)malloc(sizeof(char) * 8192);
ucontext_t ctx_main;
ucontext_t ctx_fun;
getcontext(&ctx_main);
getcontext(&ctx_fun);
printf("i = %d\n",i++);
sleep(1);
ctx_fun.uc_stack.ss_sp = stack;
ctx_fun.uc_stack.ss_size = 8192;
ctx_fun.uc_stack.ss_flags = 0;
ctx_fun.uc_link = &ctx_main;
makecontext(&ctx_fun, fun, 0);
setcontext(&ctx_fun);
printf("main exit\n");
return 0;
}
解釋一波:main 函式首先 getcontext(),獲取 main 函式的背景關系 ctx_main,ctx_fun 在下邊使用了 makecontext(),使得 ctx_fun 的背景關系變成了 fun()函式,所以程式會先列印一個 i = X,接著去 fun 函式列印 fun() 因為 ctx_fun 的連接的 ctx_main 所以又陷入一個回圈,列印完 i 的值,列印 fun()

來個代碼感受 swapcontext();
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>
ucontext_t ctx_f1;
ucontext_t ctx_f2;
ucontext_t ctx_main;
void fun1() {
printf("fun1() start\n");
swapcontext(&ctx_f1, &ctx_f2);
printf("fun1() end\n");
}
void fun2() {
printf("fun2() start\n");
swapcontext(&ctx_f2, &ctx_f1);
printf("fun2() end\n");
}
int main() {
char stack1[1024 * 8];
char stack2[1024 * 8];
getcontext(&ctx_main);
getcontext(&ctx_f1);
getcontext(&ctx_f2);
ctx_f1.uc_stack.ss_sp = stack1;
ctx_f1.uc_stack.ss_size = 8192;
ctx_f1.uc_stack.ss_flags = 0;
ctx_f1.uc_link = &ctx_f2;
makecontext(&ctx_f1, fun1, 0);
ctx_f2.uc_stack.ss_sp = stack2;
ctx_f2.uc_stack.ss_size = 8192;
ctx_f2.uc_stack.ss_flags = 0;
ctx_f2.uc_link = &ctx_main;
makecontext(&ctx_f2, fun2, 0);
swapcontext(&ctx_main, &ctx_f1);
printf("main exit\n");
}
解釋:
首先初始化 ctx_f1 ,它的后繼是 ctx_f2,接著 makecontext() 構造背景關系,ctx_f1 的背景關系是 fun1() 函式;
初始化 ctx_f2,它的后繼是 ctx_main ,接著 makecontext() 構造背景關系, ctx_f2 的背景關系是 fun2() 函式,
執行流程:首先主函式遇到 swapcontext(&ctx_main, &ctx_f1),切換到 fun1() 函式,進入 fun1() 函式遇到 swapcontext(&ctx_f1, &ctx_f2) ,切換到 fun2() 函式,進入fun2() 函式遇到 swapcontext(&ctx_f2, &ctx_f1) ,回傳 fun1()函式,fun1() 函式執行完,去執行 fun1() 函式的后繼 fun2() ,fun2() 函式執行完,去執行 fun2() 函式的后繼主函式,效果如下:

----------------------------------------------------分割線----------------------------------------------------
所以現在要實作協程,協程的結構體中應該有:回呼函式、協程背景關系、協程堆疊、協程狀態,還得有一個協程調度器來控制所有的協程,具體代碼如下:
enum State {
DEAD,
READY,
RUNNING,
SUSPEND
};
struct schedule;
// 協程結構體
typedef struct {
// 回呼函式
void *(*call_back)(struct schedule *s, void* args);
// 回呼函式引數
void *args;
// 協程背景關系
ucontext_t ctx;
// 協程堆疊
char stack[STACKSZ];
// 協程狀態
enum State state;
}coroutine_t;
// 協程調度器
typedef struct schedule {
// 所有協程
coroutine_t **coroutines;
// 當前正在運行的協程,如果沒有正在運行的協程為 -1
int current_id;
// 最大下標
int max_id;
// 主流程背景關系
ucontext_t ctx_main;
}schedule_t;
協程還得有以下功能的函式,具體的實作,參照最開始的鏈接
// 創建協程調度器
schedule_t *schedule_creat();
// 協程執行函式
static void* main_fun(schedule_t *s);
// 創建協程,回傳當前協程在調度器的下標
int coroutine_creat(schedule_t *s, void *(*call_back)(schedule_t *,void *args),void *args);
// 讓出 CPU
void coroutine_yield(schedule_t *s);
// 恢復 CPU
void coroutine_resume(schedule_t *s, int id);
// 洗掉協程
static void delete_coroutine(schedule_t *s, int id);
// 釋放調度器
void schedule_destroy(schedule_t *s);
// 判斷所有協程都運行完了
int schedule_finish(schedule_t *s);
// 啟動協程
void coroutine_running(schedule_t *s, int id);
有了以上函式的功能,使用協程實作網路服務器的具體思路如下:
做到一請求一協程,而不是一請求一執行緒,因為協程調度器里邊是用陣列保存的所有的協程,所以可以用一個回圈一直去遍歷這個陣列,當哪個協程需要執行的時候,就執行,如果讓出CPU了,就回圈往后遍歷看下一個,在同步的情況下實作了異步,用一個 current_id 來判斷當前協程是否運行(如果不運行 current_id 置為 -1),
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/148571.html
標籤:java
上一篇:隨機刷題記錄(就很迷)
