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

原文地址:https://www.cnblogs.com/Tattoo-Welkin/p/10335236.html