協程這個概念好久了,好多程序員是實現過這個組件的,網上關於協程的文章,博客,論壇都是汗牛充棟,在知乎,github上面也有不少大牛寫了關於協程的心得體會。突發奇想,我也來實現一個這樣的組件,並測試了一下性能。借鑑了不少大牛的思想,閱讀了不少大牛的代碼。因而把整個思考過程寫下來。實現代碼 https://github.com/wangbojing/NtyCogit
代碼簡單易讀,若是在你的項目中,NtyCo可以爲你解決些許工程問題,那就榮幸之至。程序員
下面將部分的NtyCo的代碼貼出來。github
NtyCo 支持多核多進程。ubuntu
int process_bind(void) { int num = sysconf(_SC_NPROCESSORS_CONF); pid_t self_id = syscall(__NR_gettid); printf("selfid --> %d\n", self_id); cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(self_id % num, &mask); sched_setaffinity(0, sizeof(mask), &mask); mulcore_entry(9096 + (self_id % num) * 10); }
NtyCo 上下文切換
服務器
首先來回顧一下x86_64寄存器的相關知識。x86_64 的寄存器有16個64位寄存器,分別是:%rax, %rbx, %rcx, %esi, %edi, %rbp, %rsp, %r8, %r9, %r10, %r11, %r12,ide
%r13, %r14, %r15。函數
%rax 做爲函數返回值使用的。性能
%rsp 棧指針寄存器,指向棧頂測試
%rdi, %rsi, %rdx, %rcx, %r8, %r9 用做函數參數,依次對應第1參數,第2參數。。。spa
%rbx, %rbp, %r12, %r13, %r14, %r15 用做數據存儲,遵循調用者使用規則,換句話說,就是隨便用。調用子函數以前要備份它,以防它被修改
%r10, %r11 用做數據存儲,就是使用前要先保存原值。
上下文切換,就是將CPU的寄存器暫時保存,再將即將運行的協程的上下文寄存器,分別mov到相對應的寄存器上。此時上下文完成切換。以下圖所示:
代碼以下
__asm__ ( " .text \n" " .p2align 4,,15 \n" ".globl _switch \n" ".globl __switch \n" "_switch: \n" "__switch: \n" " movq %rsp, 0(%rsi) # save stack_pointer \n" " movq %rbp, 8(%rsi) # save frame_pointer \n" " movq (%rsp), %rax # save insn_pointer \n" " movq %rax, 16(%rsi) \n" " movq %rbx, 24(%rsi) # save rbx,r12-r15 \n" " movq %r12, 32(%rsi) \n" " movq %r13, 40(%rsi) \n" " movq %r14, 48(%rsi) \n" " movq %r15, 56(%rsi) \n" " movq 56(%rdi), %r15 \n" " movq 48(%rdi), %r14 \n" " movq 40(%rdi), %r13 # restore rbx,r12-r15 \n" " movq 32(%rdi), %r12 \n" " movq 24(%rdi), %rbx \n" " movq 8(%rdi), %rbp # restore frame_pointer \n" " movq 0(%rdi), %rsp # restore stack_pointer \n" " movq 16(%rdi), %rax # restore insn_pointer \n" " movq %rax, (%rsp) \n" " ret \n" );
協程的調度器
調度器的實現,有兩種方案,一種是生產者消費者模式,另外一種多狀態運行。
邏輯代碼以下:
while (1) { //遍歷睡眠集合,將知足條件的加入到ready nty_coroutine *expired = NULL; while ((expired = sleep_tree_expired(sched)) != ) { TAILQ_ADD(&sched->ready, expired); } //遍歷等待集合,將知足添加的加入到ready nty_coroutine *wait = NULL; int nready = epoll_wait(sched->epfd, events, EVENT_MAX, 1); for (i = 0;i < nready;i ++) { wait = wait_tree_search(events[i].data.fd); TAILQ_ADD(&sched->ready, wait); } // 使用resume回覆ready的協程運行權 while (!TAILQ_EMPTY(&sched->ready)) { nty_coroutine *ready = TAILQ_POP(sched->ready); resume(ready); } }
多狀態運行
實現邏輯代碼以下:
while (1) { //遍歷睡眠集合,使用resume恢復expired的協程運行權 nty_coroutine *expired = NULL; while ((expired = sleep_tree_expired(sched)) != ) { resume(expired); } //遍歷等待集合,使用resume恢復wait的協程運行權 nty_coroutine *wait = NULL; int nready = epoll_wait(sched->epfd, events, EVENT_MAX, 1); for (i = 0;i < nready;i ++) { wait = wait_tree_search(events[i].data.fd); resume(wait); } // 使用resume恢復ready的協程運行權 while (!TAILQ_EMPTY(sched->ready)) { nty_coroutine *ready = TAILQ_POP(sched->ready); resume(ready); }
性能測試
測試環境:4臺VMWare 虛擬機
1臺服務器 6G內存,4核CPU
3臺客戶端 2G內存,2核CPU
操做系統:ubuntu 14.04
服務器端測試代碼:https://github.com/wangbojing/NtyCo
客戶端測試代碼:https://github.com/wangbojing/c1000k_test/blob/master/client_mutlport_epoll.c
按照每個鏈接啓動一個協程來測試。協程啓動數量可以達70W無異常。
BAT, 滴滴,今日頭條,美圖,美團等一線內推 技術崗位內推
QQ羣:935760465