• libco 共享栈测试分析与实现


    5. 共享栈模式

    这种做法有什么好处?其实我们可以直接想想以前的方法(每个协程单独分配栈)有什么坏处好了:

    • 以前的方法为每个协程都单独分配一段内存空间,因为是固定大小的,实际使用中协程并不能使用到这么大的内存空间,于是就会造成非常大的内存浪费(有同学一定会问为什么不用 Split Stack ,这个东西的性能有多垃圾有目共睹)。而且因为绝大多数协程使用的栈空间都极少,复制栈空间的开销非常小。

    • 因为协程的调度是非抢占的(non-preempt),而在 libco 中,切换的时机都是做 I/O 的时候,并且只有在切换的时候才会去复制栈空间,所以开销也可控


    具体原理:我们一步步来看其调用,从其中明白他的原理

    • 在协程环境初始化时,要先调用 (co_alloc_sharestack) 来分配共享栈的内容,其中第一个参数 count 是指分配多少个共享栈,stack_size 是指每个栈的大小 ,分配出来的结构名是 stShareStack_t

      • 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 出来,然后就算作是这个协程自己的栈。显然,这个时候这个空间是与其它协程共享的,因此叫「共享栈」。

    libco 源代码:example_copystack.cpp

    #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
    ", *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;
    }
    

    运行结果:

    在这里插入图片描述
    以上代码运行结果等同于下面:

    /*
    * 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
    ", *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到他自己的栈里去,然后把共享栈拿来给第二个使用即可.依次类推!!!

    类比去看:云风协程库保存和恢复协程运行栈原理讲解

    下面摘自好朋友宝彤大佬,我觉得说的很有道理^-^

    一块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
    ", *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

  • 相关阅读:
    PAT Advanced 1044 Shopping in Mars (25) [⼆分查找]
    PAT Advanced 1029 Median (25) [two pointers]
    PAT Advanced 1010 Radix(25) [⼆分法]
    PAT Basic 1070 结绳(25) [排序,贪⼼]
    PAT Basic 1023 组个最⼩数 (20) [贪⼼算法]
    PAT Basic 1020 ⽉饼 (25) [贪⼼算法]
    PAT Advanced 1070 Mooncake (25) [贪⼼算法]
    PAT Advanced 1067 Sort with Swap(0,*) (25) [贪⼼算法]
    PAT Advanced 1038 Recover the Smallest Number (30) [贪⼼算法]
    PAT Advanced 1037 Magic Coupon (25) [贪⼼算法]
  • 原文地址:https://www.cnblogs.com/Tattoo-Welkin/p/10335236.html
Copyright © 2020-2023  润新知