协程库项目-scheduler工作过程梳理(改)

Scheduler.h

#ifndef _SCHEDULER_H_
#define _SCHEDULER_H_

#include "thread.h"
#include "fiber.h"
#include <mutex>
#include <vector>
using namespace yjx 
{
class Scheduler
{
public:
    //构造函数:构造Scheduler对象(线程池中线程的数量,user_caller指定是否将主线程作为工作协程,name调度器的名称也就是标识符)
    Scheduler(size_t threads = 1, bool use_caller = true, const std::string& name = "Scheduler");
    virtual ~Scheduler();// 虚析构函数,需要派生类重写此函数,保证派生类对象能够正确调用
                    // 如果不是virtual 那么派生类中定义任何清理资源的函数都不会被执行,可能导致资源泄露,派生类对象不完全销毁的问题
    const std::string& getName() { return m_name ; } // 获取调度器的名字

public:
    static Scheduler* GetThis(); // 获取调度器对象

protected:
    // 只有类的实例才能使用该方法
    // 设置正在运行的调度器
    void SetThis();

public:
    // 设置任务队列
    // FiberOrCb : 调度任务类型可以是对象或函数指针.
    // 模板类:允许类或方法接受任意类型的参数,
    template <class FiberOrCb> // 避免代码冗余
    // 线程和线程的任务对应俩参数
    void schedulerlock(FiberOrCb fc, int thread = -1) // 这里fc就可以是任意参数,可能是函数,可能是协程
    {
        bool need_tickle; // 需要tickle,就是标记任务队列是否为空,从而判断是否需要唤醒线程
        {
            std::lock_guard<std::mutex> lock(m_mutex); // 不允许手动加锁解锁,只有等待程序结束自己析构
            // 如果为空,说明所有的线程都处于不活跃的状态idle,需要被唤醒
            need_tickle = m_tasks.empty();
            // 创建任务队列对象
            SchedulerTask task(fc, thread);
            if (task.fiber || task.cb) // 任务可能是函数可能是协程
            {
                m_tasks.push_back(task);
            }
            // 判断队列是否为空,不为空通知线程开始调度
            if (need_tickle) tickle();

        }
    }

    virtual void start(); // 启动调度器
    virtual void stop(); // 停止调度器

// 调度过程
protected:
    virtual void tickle(); // 唤醒线程
    virtual void run(); // 执行任务
    virtual void idle(); // 空闲协程,无任务时执行idle
    virtual bool stopping(); // 是否可以关闭 用于stop

    // 是否有空闲线程
    // 当调度协程进入idle(任务执行完了或者等待执行下一个任务,说明线程空闲了)空闲+1, 从idle协程返回(开始执行)空闲线程-1
    bool hasIdleThreads() {return m_idleThreadCount>0;}

// 任务:可能是协程也可能是函数,所以上面定义了一个模板类,避免冗余代码
private:
    struct SchedulerTask
    {
        std::shared_ptr<Fiber> fiber;
        std::function<void()> cb;
        int thread; // 运行指定任务的线程id

        SchedulerTask() // 初始化协程,入口函数,运行协程的线程id
        {
            fiber = nullptr;
            cb = nullptr;
            thread = -1;
        }
        // 任务是协程
        SchedulerTask(std::shared_ptr<Fiber> f, int thr) {
            fiber = f;
            thread = thr;
        }
        SchedulerTask(std::shared_ptr<Fiber>* f, int thr) {
            fiber.swap(*f); // 将内容转移指针内部的转移,引用计数不会增加,相当于把自己的给别人,自己-1,别人+1,别管谁引用,计数1次
            thread = thr;
        }
        // 任务是函数
        SchedulerTask(std::function<void()> cb, int thr) {
            cb = f;
            thread = thr;
        }
        SchedulerTask(std::shared_ptr<Fiber>* f, int thr) {
            cb.swap(*f);
            thread = thr;
        }

        void reset() // 重置 每一个任务执行完重置状态等待下一个任务,也是释放执行权
        {
            fiber = nullptr;
            cb = nullptr;
            thread = -1;
        }
    };
    

private:
    // 调度器标识符
    std::string m_name;
    // 互斥锁:用来保护任务队列, 因为只有主线程能访问调度器
    std::mutex m_mutex;
    // 线程池:初始化好的线程
    std::vector<std::shared_ptr<Thread>> m_threads;
    // 任务队列
    std::vector<SchdeulerTask> m_tasks;
    // 存储工作线程id
    std::vector<int> m_ThreadIds;
    // 需要额外创建的线程数
    size_t m_threadCount = 0;

    // 活跃的线程数
    std::atomic<size_t> m_activeThreadCount = {0};
    // 空闲的线程数
    std::atomic<size_t> m_idleThreadCount = {0};

    // 主线程是否用于工作线程
    bool m_userCaller;
    // 如果是-》需要创建额外的调度协程
    std::shared_ptr<Fiber> m_schedulerFiber;
    // 如果是-》记录主线程的id
    int m_rootThreadId = -1;


    // 调度器是否关闭
    bool m_stopping = false;
};


}

#endif

1. 主线程自身参与调度器调度的情况(user_caller == true),主线程在空闲状态也去执行任务,减少cpu资源浪费,主线程只有在stop()的时候才开始工作

主线程创建主协程负责执行main(),创建调度协程负责调度器。刚创建主协程时会把调度协程默认设置为主协程,此时主线程调用reset()再创建一个协程并通过方法Fiber::SetSchedulerFiber(Fiber* f) 将其设置为调度协程,该调度协程绑定了run

std::shared_ptr<Fiber> scheduler = make_shared<Scheduler>(n, true, name_);

主协程创建线程池,负责执行任务,该方法会根据n创建含有n-1一个工作线程的线程池,因为主线程也参与调度,即也能作为工作协程去工作,所以是n-1

scheduler->start()

然后开始给任务队列添加任务(到此时控制权都还在主协程手里,工作线程也没有被唤醒)

scheduler->schedulerLock(task)

最后主协程->stop(),主线程的控制权给他自己的调度协程去创建子协程工作,

scheduler->stop()

在这里首先会确认控制权,即当前运行的协程,然后将所有的线程都唤醒,避免挂起,导致永久阻塞在等待唤醒的状态,然后确认调度协程,唤醒并resume,此时调度协程才拿到控制权,调度器开始工作,取任务并调度工作线程执行任务,通过互斥锁来实现同步,然后主线程等待所有工作线程执行完毕回收资源join(), 继续执行main(),程序结束。

这里要补充一下,只要启动了线程池,工作线程就开始轮询任务队列,每个线程自己调度自己,主线程也充当工作线程的时机(参与工作的时机)就是主线程要关闭调度器,把控制权给到调度协程也去参与工作即 工作线程一创建就开始轮询, 主线程最后加入清理

2. 主线程不参与调度,只负责创建调度器,额外创建一个线程来进行调度工作

具体流程如下图所示,关于tickle()唤醒操作则是在io+scheduler中实现,目前只要工作线程被创建了就会处于一个忙等待状态,自身的主协程和空闲协程会一直来回切换,直到任务到来执行任务具体工作流程如下图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值