libco 共享栈测试分析与实现

5. 共享栈模式

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

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

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


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

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

    • 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\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

相关文章
相关标签/搜索