这种作法有什么好处?其实咱们能够直接想一想之前的方法(每一个协程单独分配栈)有什么坏处好了: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