Sylar服务器框架——协程调度器

1、协程调度器介绍

  • 协程模块中,对于每个协程,都需要用户手动调用协程的resume方法将协程运行起来,然后等协程运行结束并返回,再运行下一个协程。这种运行协程的方式其实是用户自己在挑选协程执行,相当于用户在充当调度器,显然不够灵活。
  • 协程调度器应该具有自动消耗协程的功能,程序员只需要把需要执行的协程喂给调度器,调度器就可以自己消耗掉这些协程。
  • sylar框架中的协程调度器是一个N-M的协程调度器,N个线程上可以运行M个协程,协程可以在线程之间进行切换,也可以指定某个协程在哪个线程上执行。
  • 该模块的难点是当caller线程也参与调度时调度协程和主线程切换的情况
    2、设计思路
  • 调度器创建后,内部首先会创建一个调度线程池,调度开始后,所有调度线程按顺序从任务队列里取任务执行,调度线程数越多,能够同时调度的任务也就越多,当所有任务都调度完后,调度线程就停下来等新的任务进来。接下来是添加调度任务,添加调度任务的本质就是往调度器的任务队列里塞任务,但是,只添加调度任务是不够的,还应该有一种方式用于通知调度线程有新的任务加进来了,因为调度线程并不一定知道有新任务进来了。当然调度线程也可以不停地轮询有没有新任务,但是这样CPU占用率会很高。接下来是调度器的停止。调度器应该支持停止调度的功能,以便回收调度线程的资源,只有当所有的调度线程都结束后,调度器才算真正停止。
  • 首先是协程调度器的初始化 。sylar的协程调度器在初始化时支持传入线程数和一个布尔型的use_caller参数,表示是否使用caller线程。在使用caller线程的情况下,线程数自动减一,并且调度器内部会初始化一个属于caller线程的调度协程并保存起来(比如,在main函数中创建的调度器,如果use_caller为true,那调度器会初始化一个属于main函数线程的调度协程)。调度器创建好后 ,即可调用调度器的schedule方法向调度器添加调度任务,但此时调度器并不会立刻执行这些任务,而是将它们保存到内部的一个任务队列中。接下来是调用start方法启动调度 。start方法调用后会创建调度线程池,线程数量由初始化时的线程数和use_caller确定。调度线程一旦创建,就会立刻从任务队列里取任务执行。比较特殊的一点是,如果初始化时指定线程数为1且use_caller为true,那么start方法什么也不做,因为不需要创建新线程用于调度。并且,由于没有创建新的调度线程,那只能由caller线程的调度协程来负责调度协程,而caller线程的调度协程的执行时机与start方法并不在同一个地方。caller线程的调度协程的执行时机在stop函数中。接下来是调度协程,对应run方法 。调度协程负责从调度器的任务队列中取任务执行。取出的任务即子协程,这里调度协程和子协程的切换模型即为前一章介绍的非对称模型,每个子协程执行完后都必须返回调度协程,由调度协程重新从任务队列中取新的协程并执行。如果任务队列空了,那么调度协程会切换到一个idle协程,这个idle协程什么也不做,等有新任务进来时,idle协程才会退出并回到调度协程,重新开始下一轮调度。接下来是添加调度任务,对应schedule方法 ,这个方法支持传入协程或函数,并且支持一个线程号参数,表示是否将这个协程或函数绑定到一个具体的线程上执行。如果任务队列为空,那么在添加任务之后,要调用一次tickle方法以通知各调度线程的调度协程有新任务来了。在执行调度任务时,还可以通过调度器的GetThis()方法获取到当前调度器,再通过schedule方法继续添加新的任务,这就变相实现了在子协程中创建并运行新的子协程的功能。**接下来是调度器的停止。**调度器的停止行为要分两种情况讨论,首先是use_caller为false的情况,这种情况下,由于没有使用caller线程进行调度,那么只需要简单地等各个调度线程的调度协程退出就行了。如果use_caller为true,表示caller线程也要参于调度,这时,调度器初始化时记录的属于caller线程的调度协程就要起作用了,在调度器停止前,应该让这个caller线程的调度协程也运行一次,让caller线程完成调度工作后再退出。如果调度器只使用了caller线程进行调度,那么所有的调度任务要在调度器停止时才会被调度。
    3、代码实现
  • 协程调度器成员变量
cpp 复制代码
/// 协程调度器名称
std::string m_name;
/// 互斥锁
MutexType m_mutex;
/// 线程池
std::vector<Thread::ptr> m_threads;
/// 任务队列
std::list<ScheduleTask> m_tasks;
/// 线程池的线程ID数组
std::vector<int> m_threadIds;
/// 工作线程数量,不包含use_caller的主线程
size_t m_threadCount = 0;
/// 活跃线程数
std::atomic<size_t> m_activeThreadCount = {0};
/// idle线程数
std::atomic<size_t> m_idleThreadCount = {0};
 
/// 是否use caller
bool m_useCaller;
/// use_caller为true时,调度器所在线程的调度协程
Fiber::ptr m_rootFiber;
/// use_caller为true时,调度器所在线程的id
int m_rootThread = 0;
 
/// 是否正在停止
bool m_stopping = false;
  • start() 开始调度
cpp 复制代码
void Scheduler::start() {
    SYLAR_LOG_DEBUG(g_logger) << "start";
    MutexType::Lock lock(m_mutex);
    if (m_stopping) {
        SYLAR_LOG_ERROR(g_logger) << "Scheduler is stopped";
        return;
    }
    SYLAR_ASSERT(m_threads.empty());
    m_threads.resize(m_threadCount);
    for (size_t i = 0; i < m_threadCount; i++) {
        m_threads[i].reset(new Thread(std::bind(&Scheduler::run, this),
                                      m_name + "_" + std::to_string(i)));
        m_threadIds.push_back(m_threads[i]->getId());
    }
}
  • 调度方法
cpp 复制代码
 void Scheduler::run() {
        SYLAR_LOG_DEBUG(g_logger) << m_name << " run";
        setThis();
        set_hook_enable(true);
        // 少用系统调用
        // if(sylar::GetThreadId() != m_rootThread) {
        // 第二个条件是将use_caller为true时,非caller线程中的t_scheduler_fiber赋值,caller线程中的t_scheduler_fiber在Scheduler初始化时已经赋值了
        // if (!m_useCaller || (t_scheduler_fiber == nullptr && m_useCaller)) {
            t_scheduler_fiber = Fiber::GetThis();
        // }
        //初始话一个没有任务时执行的协程
        Fiber::ptr idle_fiber(new Fiber(std::bind(&Scheduler::idle, this)));
        Fiber::ptr cb_fiber;
        ScheduleTask task;
        while(true) {
            task.reset();
            bool need_tickle = false;
            {
                MutexType::Lock lock(m_lock);
                //这个for的作用是找到一个可以给当前线程执行的任务
                auto it = m_tasks.begin();
                for(; it != m_tasks.end(); ++it) {
                    if (it ->threadid != -1 && it->threadid != sylar::GetThreadId()) {
                        need_tickle = true;
                        continue;
                    }

                    SYLAR_ASSERT(it->cb || it->fiber);
                    // 状态可以再加一个assert
                    task = *it;
                    ++m_activeThreadCount;
                    m_tasks.erase(it++);
                    break;
                }
                // 通知其他线程
                need_tickle |= (it  != m_tasks.end());
            }

            if(need_tickle) {
                tickle();
            }

            if(task.fiber) {
                // 这个任务是fiber
                task.fiber->swapIn();
                --m_activeThreadCount;

                if(task.fiber->getState() == Fiber::READY) {
                    schedule(task.fiber);
                } else if(task.fiber->getState() != Fiber::TERM
                    && task.fiber->getState() != Fiber::EXCEPT) {
                    task.fiber->setState(Fiber::HOLD);
                }
                // 让fiber资源尽早释放,不用等到下个fiber来的时候再释放
                task.reset();
            } else if(task.cb) {
                // 这个任务是callback
                if(cb_fiber) {
                    cb_fiber->reset(task.cb, m_useCaller);
                } else {
                    cb_fiber.reset(new Fiber(task.cb));
                }
                task.reset();
                cb_fiber->swapIn();
                --m_activeThreadCount;
                if(cb_fiber->getState() == Fiber::READY) {
                    schedule(task.fiber);
                } else if(cb_fiber->getState() != Fiber::TERM
                    && cb_fiber->getState() != Fiber::EXCEPT) {
                    cb_fiber->setState(Fiber::HOLD);
                }
                // 让fiber资源尽早释放,不用等到下个cb来的时候再释放
                cb_fiber.reset();
            } else {
                // 进到这个分支情况一定是任务队列空了,调度idle协程即可
                if (idle_fiber->getState() == Fiber::TERM) {
                    // 如果调度器没有调度任务,那么idle协程会不停地切换调度协程和idle协程,不会结束,
                    //如果idle协程结束了,那一定是调度器停止了,在stop函数中控制
                    SYLAR_LOG_DEBUG(g_logger) << "idle fiber term";
                    break;
                }

                ++m_idleThreadCount;
                idle_fiber->swapIn();
                --m_idleThreadCount;
                if(idle_fiber->getState() != Fiber::TERM
                    && idle_fiber->getState() != Fiber::EXCEPT) {
                    idle_fiber->setState(Fiber::HOLD);
                }
            }
        }
        // SYLAR_LOG_DEBUG(g_logger) << t_scheduler_fiber.use_count();
        // t_scheduler_fiber = nullptr;
    }
  • stop() 停止调度器
cpp 复制代码
void Scheduler::stop() {
        SYLAR_LOG_DEBUG(g_logger) << "stop";
        if (stopping()) {
            return;
        }
        // 这里控制会控制idle协程的退出
        m_stopping = true;
        /// 只能由创建线程发起scheduler的线程发起stop
        // 当m_useCaller为true时,创建线程发起scheduler的线程中GetThis() == this
        // 当m_useCaller为false时,创建线程发起scheduler的线程中GetThis() == nullptr
        if (m_useCaller) {
            SYLAR_ASSERT(GetThis() == this);
        } else {
            SYLAR_ASSERT(GetThis() != this);
        }

        for (size_t i = 0; i < m_thread_nums; i++) {
            tickle();
        }

        // 这里的主要目的,use_caller是ture的时候,在这里让caller的调度协程进行任务调度
        // 如果将这里删除,那么caller线程永远不会进行任务调度
        if (m_scheduleFiber) {
            tickle();
            m_scheduleFiber->swapIn();
        }

        // 目的是防止调度线程先执行完,销毁了Scheduler对象
        // 即使使用detach也是不可以的,这里涉及到多个线程使用Scheduler对象,如果线程还没退出对象销毁了,这个时候就会造成段错误
        std::vector<Thread::ptr> thrs;
        {
            MutexType::Lock lock(m_lock);
            thrs.swap(m_threads);
        }
        for (auto &i : thrs) {
            i->join();
        }
        SYLAR_LOG_DEBUG(g_logger) << t_scheduler_fiber.use_count();
    }
相关推荐
韩楚风4 小时前
【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
linux·服务器·性能优化·架构·gnu
陈苏同学4 小时前
4. 将pycharm本地项目同步到(Linux)服务器上——深度学习·科研实践·从0到1
linux·服务器·ide·人工智能·python·深度学习·pycharm
Ambition_LAO4 小时前
解决:进入 WSL(Windows Subsystem for Linux)以及将 PyCharm 2024 连接到 WSL
linux·pycharm
Pythonliu74 小时前
茴香豆 + Qwen-7B-Chat-Int8
linux·运维·服务器
你疯了抱抱我4 小时前
【RockyLinux 9.4】安装 NVIDIA 驱动,改变分辨率,避坑版本。(CentOS 系列也能用)
linux·运维·centos
追风赶月、4 小时前
【Linux】进程地址空间(初步了解)
linux
栎栎学编程4 小时前
Linux中环境变量
linux
我是哈哈hh4 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
郭二哈4 小时前
C++——模板进阶、继承
java·服务器·c++
挥剑决浮云 -5 小时前
Linux 之 安装软件、GCC编译器、Linux 操作系统基础
linux·服务器·c语言·c++·经验分享·笔记