這種作法有什麼好處?其實咱們能夠直接想一想之前的方法(每一個協程單獨分配棧)有什麼壞處好了:git
之前的方法爲每一個協程都單獨分配一段內存空間,由於是固定大小的,實際使用中協程並不能使用到這麼大的內存空間,因而就會形成很是大的內存浪費(有同窗必定會問爲何不用 Split Stack
,這個東西的性能有多垃圾有目共睹)。並且由於絕大多數協程使用的棧空間都極少,複製棧空間的開銷很是小。github
由於協程的調度是非搶佔的(non-preempt),而在 libco 中,切換的時機都是作 I/O 的時候,而且只有在切換的時候纔會去複製棧空間,因此開銷也可控。web
具體原理:咱們一步步來看其調用,從其中明白他的原理express
在協程環境初始化時,要先調用 (co_alloc_sharestack
) 來分配共享棧的內容,其中第一個參數 count 是指分配多少個共享棧,stack_size 是指每一個棧的大小 ,分配出來的結構名是 stShareStack_t
。apache
stShareStack_t
結構struct stShareStack_t { unsigned int alloc_idx; int stack_size; int count; stStackMem_t **stack_array; };
co_alloc_sharestack
//建立 count 個共享棧,大小爲 stack_size stShareStack_t* co_alloc_sharestack(int count, int stack_size) { stShareStack_t* share_stack = (stShareStack_t*)malloc(sizeof(stShareStack_t)); share_stack->alloc_idx = 0;//初始化起始的分配遊標 share_stack->stack_size = stack_size; //alloc stack array share_stack->count = count; //初始化棧空間 stStackMem_t** stack_array = (stStackMem_t**)calloc(count, sizeof(stStackMem_t*)); for (int i = 0; i < count; i++) { stack_array[i] = co_alloc_stackmem(stack_size); } share_stack->stack_array = stack_array; return share_stack; }
共享棧的結構是一個數組,它裏面有 count
個元素,每一個元素都是一個指向一段內存的指針 stStackMem_t
。在新分配協程時 (co_create_env)
,它會從剛剛分配的 stShareStack_t
中,按 RoundRobin
的方式取一個 stStackMem_t
出來,而後就算做是這個協程本身的棧。顯然,這個時候這個空間是與其它協程共享的,所以叫「共享棧」。數組
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/time.h> #include <errno.h> #include <string.h> #include "coctx.h" #include "co_routine.h" #include "co_routine_inner.h" void *RoutineFunc(void *args) { co_enable_hook_sys(); int *routineid = (int *)args; while (true) { char sBuff[128]; sprintf(sBuff, "from routineid %d stack addr %p\n", *routineid, sBuff); printf("%s", sBuff); poll(NULL, 0, 1000); //sleep 1s } return NULL; } int main() { stShareStack_t *share_stack = co_alloc_sharestack(1, 1024 * 128); stCoRoutineAttr_t attr; attr.stack_size = 0; attr.share_stack = share_stack; stCoRoutine_t *co[2]; int routineid[2]; for (int i = 0; i < 2; i++) { routineid[i] = i; co_create(&co[i], &attr, RoutineFunc, routineid + i); co_resume(co[i]); } co_eventloop(co_get_epoll_ct(), NULL, NULL); return 0; }
以上代碼運行結果等同於下面:app
/* * Tencent is pleased to support the open source community by making Libco available. * Copyright (C) 2014 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/time.h> #include <errno.h> #include <string.h> #include "coctx.h" #include "co_routine.h" #include "co_routine_inner.h" void *RoutineFunc(void *args) { // co_enable_hook_sys(); int *routineid = (int *)args; while (true) { char sBuff[128]; sprintf(sBuff, "from routineid %d stack addr %p\n", *routineid, sBuff); printf("%s", sBuff); // poll(NULL, 0, 1000); //sleep 1s // sleep(1); co_yield(); } return NULL; } int main() { stShareStack_t *share_stack = co_alloc_sharestack(1, 1024 * 128); stCoRoutineAttr_t attr; attr.stack_size = 0; attr.share_stack = share_stack; stCoRoutine_t *co[2]; int routineid[2]; for (int i = 0; i < 2; i++) { routineid[i] = i; co_create(&co[i], &attr, RoutineFunc, routineid + i); } // co_eventloop(co_get_epoll_ct(), NULL, NULL); while (true) { co_resume(co[0]); co_resume(co[1]); } return 0; }
首先經過co_alloc_sharestack(1, 1024 * 128);
分配一個1024*128
的共享棧空間,而後將要建立的協程的參數設置爲使用這塊共享棧空間,以後建立並調用,eventloop
先不用管,hook層
主要實現了在遇到阻塞IO時自動切換協程,(如何阻塞由事件循環co_eventloop
檢測的)阻塞IO完成時恢復協程,簡化異步回調爲相對同步方式的功能.那麼這樣看來就是在sleep
的時候,程序返回到主協程執行for
循環,當調用到第二個協程執行的時候,他也要使用這個共享棧,因此內部就是將第一個子協程的使用到的數據copy
到他本身的棧裏去,而後把共享棧拿來給第二個使用便可.依次類推!!!less
類比去看:雲風協程庫保存和恢復協程運行棧原理講解異步
下面摘自好朋友寶彤大佬,我以爲說的頗有道理^-^
svg
一塊share stack上的一個棧由多個協程共享,當一個協程要使用stack時,上一個協程要讓出來(將棧內有效數據保存到本身的控制字內),而後新協程使用共享棧空間直到其餘公用這塊棧的協程要使用到他,不然它就一直佔用這塊棧空間(無論它是否在運行)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/time.h> #include <errno.h> #include <string.h> #include "coctx.h" #include "routine.h" #include "routine.cpp" using namespace Tattoo; Routine_t *co[2]; void *RoutineFunc(void *args) { // co_enable_hook_sys(); int *routineid = (int *)args; while (true) { char sBuff[128]; sprintf(sBuff, "from routineid %d stack addr %p\n", *routineid, sBuff); printf("%s", sBuff); // poll(NULL, 0, 1000); //sleep 1s // sleep(1); co[*routineid]->Yield(); } return NULL; } int main() { ShareStack_t *share_stack = new ShareStack_t(1, 1024 * 128); // ShareStack_t *share_stack = co_alloc_sharestack(1, 1024 * 128); RoutineAttr_t attr(0, share_stack); int routineid[2]; for (int i = 0; i < 2; i++) { routineid[i] = i; co[i] = new Routine_t(get_curr_thread_env(), &attr, RoutineFunc, routineid + i); } // co_eventloop(co_get_epoll_ct(), NULL, NULL); while (true) { co[1]->Resume(); sleep(1); co[0]->Resume(); } return 0; }
協程基本上就最最最基礎的就算完成了,下來的計劃就是 eventloop(參考muduo) -> conditional_variable ->內存泄露 -> hook層等等
代碼地址:MyLibCo
求 star ,fork