RadosGW的Coroutine浅析

本文深入探讨了RadosGW中Coroutine的应用,分析了其引入Coroutine的原因及如何通过boost::asio::coroutine实现在用户态的操作系统组件模拟多线程并发处理,特别关注于跨区域复制(multisite)场景下的效率提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RadosGW的Coroutine浅析

参考资料

  1. Asio 协程详解

  2. v12.2.12

RadosGW使用boost::asio::coroutine的原因

之前,RadosGW所使用的模型都是多线程同步模型。比如处理S3/swift请求的就是rgw_thread_pool_size个线程,每个线程在处理完一个请求前,被完全占用,不能处理其他请求。

RGWCoroutine是社区在实现跨区域复制(multisite)的时候引入的。根据multisite的原理,ZoneGroup里有N个Zone,每个Zone里有M个datalog shard的话,按照原来的多线程模型,每个RGW需要O(N*M)个线程来负责与其他N-1个Zone之间的数据同步。而从代码看,社区只想用一个元数据同步线程和N个数据同步线程就完成成语N个Zone的同步。这就意味着一个线程需要有能力在User Mode做到不同的同步任务之间切换,从而虚拟出多线程的假象。

一般情况下,程序员并不需要在一个C++函数中标记说,哪些位置可以进行线程切换。做线程切换的决策完全交给Kernel Mode处理。线程上下文的保存和切换也是Kernel Mode自动完成。而在multisite实现中,由于需要实际的线程数有限,不能让一个函数完全霸占,需要在适当的时候,User Mode识别出函数在哪里可以搞一段落,暂时中断,过一会儿再执行。boost::asio::coroutine中一个个yield语句,本质上就是做这样的标记。RGWCoroutine::operate()中每个yieldboost::asio::coroutine的成员变量中记录下当前的执行位置,并使RGWCoroutine::operate()返回,这时,真实的线程就可以先去处理其他任务,过一会儿再来调用这个RGWCoroutine::operate()时,根据boost::asio::coroutine的标记,再继续执行。所以,boost::asio::coroutine是用来在User Mode实现上下文的保存的工具。

RadosGW基于Coroutine的用户态操作系统

组件映射关系

RadosGW中的概念操作系统中的概念
RGWCoroutineFunction/Method
RGWCoroutinsStackThread
ThreadCPU Core
contextProcess
RGWCoroutinesEnvContext-related Registers
RGWCoroutinesManagerThread Scheduler
RGWCompletionManagerTimer/Mutex

RGWCoroutine是一个抽象类,multisite的代码中大量定义RGWCoroutine的子类,以重载其RGWCoroutine::operate()方法,从而实现不同的功能。RGWCoroutine相当于C++中的functor,不同的是它利用boost::asio::coroutine技术在User Mode将operate()方法切割成了多个可以中断的部分。另外,RGWCoroutine::operate()的返回值是int类型,不支持其他类型的返回值。

RGWCoroutinsStack对应的是线程的概念。这里叫XXXStack也是合理,因为其主体内容其实就是一系列RGWCoroutine组成的一个Stack,执行顺序和传统意义上的函数调用栈也是一致的。

  • RGWCoroutine::call()本质上就是将一个RGWCoroutine放置到当前RGWCoroutinsStack的顶端,就类似于传统的函数调用。
  • RGWCoroutine::spawn()本质上就是创建一个新的RGWCoroutinsStack,并将一个RGWCoroutine放在新的RGWCoroutinsStack的顶部,就类似于传统的pthread_create()

multisite中实体的Thread主要是RGWSyncProcessorThread的子类RGWMetaSyncProcessorThreadRGWDataSyncProcessorThread,这些线程最终调用了RGWCoroutinesManager::run()以达到用RGWCoroutine模拟更多线程的目的。这些线程可类比CPU的core。

context是存放在RGWCoroutinesManager::run_contexts里。RGWCoroutinesManager::run_contexts是一个Map,key是整数,value是set<RGWCoroutinesStack *>。目前看来这个概念虽然和进程对的上号,但multisite并没有使用到多个context的情况。

RGWCoroutinesEnv是RGWCoroutinesManager::run(list<RGWCoroutinesStack *>& stacks)中的一个局部变量,用来记住当前正在处理的是哪个context(对应进程),改context中哪些RGWCoroutinsStack正要被执行(对应Ready的线程),当前正在执行的是那个RGWCoroutinsStack(对应当前线程)。

RGWCoroutinesManager的主要逻辑在RGWCoroutinesManager::run(list<RGWCoroutinesStack *>& stacks),这个成员函数负责不停地将可以执行的RGWCoroutinsStack找出来,并执行其顶部的RGWCoroutine

在传统的多线程难免需要sleep或等待其他线程完成特定操作的情况,RGWCompletionManager负责为RGWCoroutine提供这些基础设施。

RGWCoroutinesManager::run()

RGWCoroutinesManager::run(list<RGWCoroutinesStack *>& stacks)是用户态操作系统的核心逻辑,简单说它就是创建了一个虚拟进程(context),然后运行里面的虚拟线程(RGWCoroutinsStack),知道这个虚拟进程结束。这里对这个代码进行一下注释:

// 这里传进来的stacks可认为是一组初始的等待执行的线程,一般只有一个RGWCoroutinesStack,即相当于那个主线程
int RGWCoroutinesManager::run(list<RGWCoroutinesStack *>& stacks)
{
  int ret = 0;
  int blocked_count = 0;
  int interval_wait_count = 0;
  bool canceled = false; // set on going_down
  // 定义RGWCoroutinesEnv这个局部变量
  RGWCoroutinesEnv env;

  // 申请一个新的context编号,一般从run_contexts获取对应的set<RGWCoroutinesStack *>
  // 由于run_context_count的初始值为0,所以context编号>0
  uint64_t run_context = ++run_context_count;

  lock.get_write();
  // 初始化RGWCoroutinesEnv的各个成员变量,其中scheduled_stacks类似于操作系统中的ready线程的队列
  set<RGWCoroutinesStack *>& context_stacks = run_contexts[run_context];
  list<RGWCoroutinesStack *> scheduled_stacks;
  for (auto& st : stacks) {
    context_stacks.insert(st);
    scheduled_stacks.push_back(st);
  }
  lock.unlock();

  env.run_context = run_context;
  env.manager = this;
  env.scheduled_stacks = &scheduled_stacks;

  // 相当于在操作系统里不停遍历ready线程的队列,对线程进行执行
  for (list<RGWCoroutinesStack *>::iterator iter = scheduled_stacks.begin(); iter != scheduled_stacks.end() && !going_down;) {
    lock.get_write();

    RGWCoroutinesStack *stack = *iter;
    // 修改env.stack指向当前的虚拟线程
    env.stack = stack;

    // 执行虚拟线程顶部的RGWCoroutine的一部分
    ret = stack->operate(&env);
    stack->set_is_scheduled(false);
    if (ret < 0) {
      ldout(cct, 20) << "stack->operate() returned ret=" << ret << dendl;
    }

    if (stack->is_error()) {
      report_error(stack);
    }

    bool op_not_blocked = false;

    if (stack->is_io_blocked()) {
      // 记录一些统计信息,打些日志
      ldout(cct, 20) << __func__ << ":" << " stack=" << (void *)stack << " is io blocked" << dendl;
      if (stack->is_interval_waiting()) {
        interval_wait_count++;
      }
      blocked_count++;
    } else if (stack->is_blocked()) {
      /* do nothing, we'll re-add the stack when the blocking stack is done,
       * or when we're awaken
       */
      // 打些日志
      ldout(cct, 20) << __func__ << ":" << " stack=" << (void *)stack << " is_blocked_by_stack()=" << stack->is_blocked_by_stack()
	             << " is_sleeping=" << stack->is_sleeping() << " waiting_for_child()=" << stack->waiting_for_child() << dendl;
    } else if (stack->is_done()) {
      ldout(cct, 20) << __func__ << ":" << " stack=" << (void *)stack << " is done" << dendl;
      RGWCoroutinesStack *s;
      // 当前虚拟线程执行结束,看看有没有其他虚拟线程正在等待本线程,有的话调度一下,使其能够继续执行
      while (stack->unblock_stack(&s)) {
	if (!s->is_blocked_by_stack() && !s->is_done()) {
	  if (s->is_io_blocked()) {
            if (stack->is_interval_waiting()) {
              interval_wait_count++;
            }
	    blocked_count++;
	  } else {
	    s->schedule();
	  }
	}
      }
      // 如果否父虚拟线程(即RGWCoroutine::spawn出本虚拟线程的那个虚拟线程)等待本线程结束,
      // 那么激活那个父虚拟线程
      if (stack->parent && stack->parent->waiting_for_child()) {
        stack->parent->set_wait_for_child(false);
        stack->parent->schedule();
      }
      // 既然本虚拟线程执行完了,就从context_stacks清理掉,消减引用数
      context_stacks.erase(stack);
      stack->put();
      stack = NULL;
    } else {
      // 本虚拟线程还有RGWCoroutine需要执行,而且目前没有被阻塞,那就再加入队列中,等待执行
      op_not_blocked = true;
      stack->run_count++;
      stack->schedule();
    }

    if (!op_not_blocked && stack) {
      stack->run_count = 0;
    }

    lock.unlock();

    RGWCoroutinesStack *blocked_stack;
    // 检查一下有没有等待定时器或其他原因被阻塞的虚拟线程已经可以被唤醒了,有的话拿出来,加入执行队列里
    // 注意这里的try_get_next是非阻塞的,当没有可以被唤醒的虚拟线程时,立刻返回false
    while (completion_mgr->try_get_next((void **)&blocked_stack)) {
      handle_unblocked_stack(context_stacks, scheduled_stacks, blocked_stack, &blocked_count);
    }

    /*
     * only account blocked operations that are not in interval_wait, these are stacks that
     * were put on a wait without any real IO operations. While we mark these as io_blocked,
     * these aren't really waiting for IOs
     */
    // 当被IO阻塞的虚拟线程太多时,则尽可能等待,以消减被阻塞的虚拟线程
    while (blocked_count - interval_wait_count >= ops_window) {
      // 注意这里的get_next和try_get_next不同
      // get_next如果发现没有虚拟线程可以被唤醒时,会一直阻塞到至少一个虚拟线程可以被唤醒
      ret = completion_mgr->get_next((void **)&blocked_stack);
      if (ret < 0) {
       ldout(cct, 5) << "completion_mgr.get_next() returned ret=" << ret << dendl;
      }
      handle_unblocked_stack(context_stacks, scheduled_stacks, blocked_stack, &blocked_count);
    }
    // 准备执行下一个虚拟线程
    ++iter;
    // 将刚执行的虚拟线程剔出队列
    scheduled_stacks.pop_front();

    // 如果ready队列里没有可执行的虚拟线程了,而且还有虚拟线程被阻塞,那么就一直等待到有线程能被唤醒
    while (scheduled_stacks.empty() && blocked_count > 0) {
      ret = completion_mgr->get_next((void **)&blocked_stack);
      if (ret < 0) {
        ldout(cct, 5) << "completion_mgr.get_next() returned ret=" << ret << dendl;
      }
      if (going_down) {
	ldout(cct, 5) << __func__ << "(): was stopped, exiting" << dendl;
	ret = -ECANCELED;
        canceled = true;
        break;
      }
      handle_unblocked_stack(context_stacks, scheduled_stacks, blocked_stack, &blocked_count);
      iter = scheduled_stacks.begin();
    }
    if (canceled) {
      break;
    }

    // 到达队尾,从头开始执行虚拟线程
    if (iter == scheduled_stacks.end()) {
      iter = scheduled_stacks.begin();
    }
  }

  lock.get_write();
  // 打印些日志
  if (!context_stacks.empty() && !going_down) {
    JSONFormatter formatter(true);
    formatter.open_array_section("context_stacks");
    for (auto& s : context_stacks) {
      ::encode_json("entry", *s, &formatter);
    }
    formatter.close_section();
    lderr(cct) << __func__ << "(): ERROR: deadlock detected, dumping remaining coroutines:\n";
    formatter.flush(*_dout);
    *_dout << dendl;
    assert(context_stacks.empty() || going_down); // assert on deadlock
  }
  // 虚拟进程退出前,清理一下虚拟线程
  for (auto stack : context_stacks) {
    ldout(cct, 20) << "clearing stack on run() exit: stack=" << (void *)stack << " nref=" << stack->get_nref() << dendl;
    stack->put();
  }
  // 清理掉虚拟进程
  run_contexts.erase(run_context);
  lock.unlock();

  return ret;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值