写点什么

一个 cpp 协程库的前世今生(二十二)协程偷取

作者:SkyFire
  • 2022 年 1 月 20 日
  • 本文字数:1829 字

    阅读完需:约 6 分钟

一个cpp协程库的前世今生(二十二)协程偷取

为了防止大家找不到代码,还是先贴一下项目链接:


GitHub - skyfireitdiy/cocpp at cocpp-0.1.0


前面的文章介绍了如何避免单个协程长时间占用 cpu 引起同一个调度线程上其他协程阻塞。


然而还有另一个问题需要解决,当一个调度线程上的协程全部完成后,这个线程应该怎么进行下一步动作呢?有几种选择:


  • 销毁

  • 休眠

  • 去其他调度线程中“偷取”一个可移动的协程来运行


这三个处理方式各有优缺点。在 cocpp 中都有使用,其整体策略如下。

协程空闲时的策略

在 cocpp 中,会有两个阈值:基础 env 数量 base_env_count 和最大 env 数量 max_env_count。


  • 在当前 env 数量小于 base_env_count 时,每当有新的 ctx 加入,都会新分配一个 env 来调度(因此当协程数量小于等于 base_env_count 时相当于多线程执行)。

  • 当协程数量大于 base_env_count 而小于等于 max_env_count 时,如果有新的 ctx 加入,只有在当前协程都无法调度的时候才会创建新的 env,需要注意一点,在这个范围内的 env 不会自动被销毁

  • 当协程数量大于 max_env_count 时,超过的部分空闲 ctx 会被回收。


这个策略还面临一个问题,在一个 env 中没有 ctx 可以被调度的时候,可能另外的 env 中积压了很多 ctx,此时如果直接休眠或者销毁,对整体调度的响应速度来说是不利的。


因此在 env 中没有可调度的 ctx 时,env 会尝试从其他的 env 中偷取一个 ctx 来执行,防止自身被销毁或者休眠。

协程偷取

协程偷取的流程位于定时任务中(source/cocpp/core/co_manager.cpp):


void co_manager::subscribe_manager_event__(){    timing_routine_timout().sub([this] {        // 每两次超时重新分配一次        static bool double_timeout = false;
// 强制重新调度 force_schedule__();
// 如果是第二次超时 if (double_timeout) { // 重新调度 redistribute_ctx__(); // 偷取ctx steal_ctx_routine__(); // 销毁多余的env destroy_redundant_env__(); // 释放内存 free_mem__(); } double_timeout = !double_timeout; });}
复制代码


主要实现为 steal_ctx_routine__函数。

steal_ctx_routine__

steal_ctx_routine__函数实现如下(source/cocpp/core/co_manager.cpp):



void co_manager::steal_ctx_routine__(){ std::scoped_lock lock(env_set__.normal_lock); std::vector<co_env*> idle_env_list; idle_env_list.reserve(env_set__.normal_set.size()); for (auto& env : env_set__.normal_set) { if (env->state() == co_env_state::idle) { idle_env_list.push_back(env); } }
auto iter = env_set__.normal_set.begin(); for (auto& env : idle_env_list) { for (; iter != env_set__.normal_set.end(); ++iter) { if ((*iter)->state() == co_env_state::idle) { break; } auto ctx = (*iter)->take_one_movable_ctx(); if (ctx != nullptr) { env->move_ctx_to_here(ctx); break; } } }}
复制代码


其实现分为两个部分。


  • 找出所有 env 中目前空闲的 env(这些是需要从其他 env 中偷取 ctx 的)。

  • 遍历所有的 env,为每个空闲 env 偷取一个 ctx


整个流程的逻辑非常简单。这里仅看一下 take_one_movable_ctx 函数的实现(source/cocpp/core/co_env.cpp):


co_ctx* co_env::take_one_movable_ctx(){    std::scoped_lock lock(mu_normal_ctx__, mu_min_priority__, schedule_lock__);    for (unsigned int i = min_priority__; i < all_normal_ctx__.size(); ++i)    {        auto backup = all_normal_ctx__[i];        for (auto& ctx : backup)        {            if (ctx->can_move())            {                all_normal_ctx__[i].remove(ctx);                one_moveable_ctx_taken().pub(ctx);                return ctx;            }        }    }    one_moveable_ctx_taken().pub(nullptr);    return nullptr;}
复制代码


此函数与 take_all_movable_ctx 非常类似,不同点在于,本函数找到第一个可移动的 ctx 就会返回。

总结

本文主要介绍了 env 在空闲状态采取的策略,以及协程偷取的流程,还有很多不完善之处,待后续版本优化。

发布于: 刚刚阅读数: 2
用户头像

SkyFire

关注

这个cpper很懒,什么都没留下 2018.10.13 加入

会一点点cpp的苦逼码农

评论

发布
暂无评论
一个cpp协程库的前世今生(二十二)协程偷取