站在巨人的肩膀上
C++高性能分布式服务器框架
从零开始重写sylar C++高性能分布式服务器框架
概述
- 一个 N-M 的协程调度器,N 个线程运行 M 个协程,协程可以在线程之间进行切换,协程也可以绑定到指定线程运行。
- 实现协程调度之后,可以解决前一章协程模块中子协程不能运行另一个子协程的缺陷,子协程可以通过向调度器添加调度任务的方式来运行另一个子协程。
- 协程调度器调度的是协程,函数(可执行对象)被包装成协程。
Scheduler
- 协程调度器类。
- t_scheduler_fiber 保存当前线程的调度协程,加上 Fiber 模块的 t_fiber 和 t_thread_fiber,每个线程总共可以记录三个协程的上下文信息。
SchedulerSwitcher
其他说明
- 协程调度最难理解的地方是当 caller 线程也参与调度时调度协程和主线程切换的情况。
- 调度线程可以包含 caller 线程。
- 在非 caller 线程里,调度协程就是调度线程的主协程;但在 caller 线程里,调度协程并不是 caller 线程的主协程,而是相当于 caller 线程的子协程。
- 在非对称协程里,子协程只能和线程主协程切换,而不能和另一个子协程切换。而这里,调度协程和任务协程,都是子协程,也就是说,调度协程不能直接和任务协程切换。sylar 的解决方案是:给每个线程增加一个线程局部变量用于保存调度协程的上下文就可以了,这样,每个线程可以同时保存三个协程的上下文,一个是当前正在执行的协程上下文,另一个是线程主协程的上下文,最后一个是调度协程的上下文。
- 支持添加函数或协程作为调度对象,并且支持将函数或协程绑定到一个具体的线程上执行。
- 调度协程执行 run 方法,负责从调度器的任务队列中取任务执行,取出的任务即子协程。每个子协程执行完后都必须返回调度协程,由调度协程重新从任务队列中取新的协程并执行。如果任务队列空了,那么调度协程会切换到一个 idle 协程,这个 idle 协程什么也不做,等有新任务进来前,不断地与调度协程进行切换(这里其实是忙等)。
- 如果任务队列为空,那么在添加任务之后,要调用一次 tickle 方法以通知各调度线程的调度协程有新任务来了。
- 调度器的停止行为要分两种情况讨论,首先是 use_caller 为 false 的情况,这种情况下,由于没有使用 caller 线程进行调度,那么只需要简单地等各个调度线程的调度协程退出就行了。如果 use_caller 为 true,表示 caller 线程也参与了调度,这时,调度器初始化时记录的属于 caller 线程的调度协程就要起作用了,在调度器停止前,应该让这个 caller 线程的调度协程也运行一次,让 caller 线程完成调度工作后再退出。如果调度器只使用了 caller 线程进行调度,那么所有的调度任务要在调度器停止时才会被调度(因为只使用了 caller 线程进行调度的话,就意味着用的是 caller 线程的子协程进行调度,而只有在调度器停止时,该子协程才会被 call())。
- sylar 的协程调度模块因为存任务队列空闲时调度线程忙等待的问题,所以实际上并不实用,真正实用的是后面基于 Scheduler 实现的 IOManager。
部分相关代码
/**
* @filename scheduler.h
* @brief 协程调度模块
* @author L-ge
* @version 0.1
* @modify 2022-06-29
*/
#ifndef __SYLAR_SCHEDULER_H__
#define __SYLAR_SCHEDULER_H__
#include <memory>
#include <vector>
#include <list>
#include <iostream>
#include "fiber.h"
#include "thread.h"
namespace sylar
{
/**
* @brief 协程调度器
*/
class Scheduler
{
public:
typedef std::shared_ptr<Scheduler> ptr;
typedef Mutex MutexType;
/**
* @brief 构造函数
*
* @param threads 线程数量
* @param use_caller 是否使用当前调用线程
* @param name 协程调度器名称
*/
Scheduler(size_t threads = 1, bool use_caller = true, const std::string& name = "");
virtual ~Scheduler();
const std::string& getName() const { return m_name; }
static Scheduler* GetThis();
/**
* @brief 返回当前协程调度器的调度协程(不一定是主协程)