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

相關文章
相關標籤/搜索