[libco] 协程栈空间

协程“栈”空间,有独立栈和共享栈,重点理解一下协程共享栈。

文章来源:[libco] 协程栈空间


1. 概述

libco 虽然支持海量协程,但是单线程,同一时刻只支持一个协程在工作。在一个时间段内,它通过调度,使多个协程不停切换,从而实现协程“并发”功能。

协程“栈”空间,有独立栈,也有共享栈。这个“栈”添加了引号,其实它是在堆上分配的,因为它的协程函数工作原理与普通函数工作原理差不多,所以才叫“栈”。

普通函数运行原理:《x86_64 函数运行时栈帧内存布局


2. 独立栈

协程独立栈默认使用 128k 内存空间,简单方便,一般程序使用也足够了,但是它也有缺点:

  1. 如果某个协程函数使用栈空间超过 128 k,那么内存会溢出,导致进程崩溃。(当然共享栈也会,但是没那么容易溢出。)
  2. 协程独立栈虽然默认只需要 128 k 内存,但是绝大多数使用场景,内存比这个少,每个协程分配固定的资源,还是有点浪费了。
  3. libco 号称支持千万级协程,如果每个协程都是独立栈,那得废多少内存?!
struct stCoRoutine_t *co_create_env(stCoRoutineEnv_t *env, const stCoRoutineAttr_t *attr, pfn_co_routine_t pfn, void *arg) {
    stCoRoutineAttr_t at;
    if (attr) {
        memcpy(&at, attr, sizeof(at));
    }

    if (at.stack_size <= 0) {
        /* 独立栈默认 128 k。 */
        at.stack_size = 128 * 1024;
    } else if (at.stack_size > 1024 * 1024 * 8) {
        at.stack_size = 1024 * 1024 * 8;
    }
    ...
}

3. 共享栈

基于上述独立栈的缺点,共享栈应运而生。

  1. 共享栈协程,协程在创建时,被分配在指定的共享栈内存块上工作。
  2. 当然只有正在执行的协程,才会使用共享栈,当它被(yield)切换出来后,它需要保存协程上下文:寄存器数据 + 内存数据,所以共享栈上的使用部分(不是整个共享栈空间)会被拷贝出来。
  3. 同理新切入的协程,需要将以前保存的内存上下文,重新拷贝到共享栈上工作。
  4. 内存拷贝不是必然的,因为有多个共享内存块,每个块都会被指派给多个协程,只有当相同共享栈上的协程切换才会出现内存拷贝。
  • 共享栈,协程栈空间指向指定共享栈空间。
struct stCoRoutine_t *co_create_env(stCoRoutineEnv_t *env, const stCoRoutineAttr_t *attr, pfn_co_routine_t pfn, void *arg) {
    ...
    stStackMem_t *stack_mem = NULL;
    if (at.share_stack) {
        stack_mem = co_get_stackmem(at.share_stack);
        at.stack_size = at.share_stack->stack_size;
    } else {
        stack_mem = co_alloc_stackmem(at.stack_size);
    }
    lp->stack_mem = stack_mem;
    ...
}

static stStackMem_t *co_get_stackmem(stShareStack_t *share_stack) {
    if (!share_stack) {
        return NULL;
    }
    int idx = share_stack->alloc_idx % share_stack->count;
    share_stack->alloc_idx++;
    return share_stack->stack_array[idx];
}
  • co_swap 协程切换函数很特别,coctx_swap 上面代码还是是协程 A,下面部分就是协程 B 了。
void co_swap(stCoRoutine_t *curr, stCoRoutine_t *pending_co) {
    // A coroutine. 
    // swap context
    coctx_swap(&(curr->ctx), &(pending_co->ctx));
    // B coroutine. 
}
  • 协程在切换过程中,内存拷贝。
void co_swap(stCoRoutine_t *curr, stCoRoutine_t *pending_co) {
    stCoRoutineEnv_t *env = co_get_curr_thread_env();

    //get curr stack sp
    char c;
    /* 记录当前协程空间栈底位置,因为函数局部变量都是通过压栈进入内存的,地址从高到低) */
    curr->stack_sp = &c;

    if (!pending_co->cIsShareStack) {
        ...
    } else {
        /* 因为 coctx_swap 上下代码已经不是同一个协程了,需要 env 保存信息,方便不同协程使用。 */
        env->pending_co = pending_co;
        //get last occupy co on the same stack mem
        stCoRoutine_t *occupy_co = pending_co->stack_mem->occupy_co;
        //set pending co to occupy thest stack mem;
        pending_co->stack_mem->occupy_co = pending_co;

        env->occupy_co = occupy_co;
        /* 不一定需要内存拷贝啊,新切换的协程,可能落在其它共享栈上。*/
        if (occupy_co && occupy_co != pending_co) {
            /* 当前协程被切出来了,需要从共享栈上保存它的内存上下文。 */
            save_stack_buffer(occupy_co);
        }
    }

    /* 协程切换,切换上下文。 */
    coctx_swap(&(curr->ctx), &(pending_co->ctx));

    //stack buffer may be overwrite, so get again;
    stCoRoutineEnv_t *curr_env = co_get_curr_thread_env();
    stCoRoutine_t *update_occupy_co = curr_env->occupy_co;
    stCoRoutine_t *update_pending_co = curr_env->pending_co;

    /* 不一定需要内存拷贝啊,新切换的协程,可能落在其它的共享栈上。*/
    if (update_pending_co && update_occupy_co != update_pending_co) {
        /* 当前共享栈上,当前协程是新切换进来的,那么需要把它的前面保存的内存上下文,拷贝到共享栈上运行。 */
        if (update_pending_co->save_buffer && update_pending_co->save_size > 0) {
            memcpy(update_pending_co->stack_sp, update_pending_co->save_buffer, update_pending_co->save_size);
        }
    }
}

4. 小结

  • 独立栈相对简单,但废内存,容易栈溢出。
  • 共享栈使用公共资源,公共资源内存空间比较大,相对安全,节省内存空间,但是协程频繁切换需要进行内存拷贝,废 CPU。
  • 独立栈和共享栈的实现逻辑并不复杂,协程原理理解关键在切换
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352