高性能分布式网络服务器--协程调度器

协程调度器

在学习协程调度器之前必须掌握协程

当你有很多协程时,如何把这些协程都消耗掉,这就是设计协程调度器的目的。在协程模块中,对于每个协程,都需要使用者手动调用协程的resume方法将协程运行起来,然后等协程运行结束或者挂起,再选择下一个协程运行。这种运行协程的方式其实是使用者自己在挑选协程执行,相当于使用者充当调度器,显然不够灵活,协程的使用成本也非常高

然而,在我们引入调度器之后。我们就可以先创建一个调度器,再将创建好的协程添加到调度器,由调度器负责把这些协程一个一个消耗掉。这样使用者就不必操心协程何时应该挂起,下一个协程应该如何选择

github

https://github.com/huxiaohei/tiger.git

实现

作为一个完善的调度器应该满足一下几点需求

  • 支持多线程
  • 支持绑定一个任务到指定线程
  • 支持将函数和协程作为调度任务
    在这里插入图片描述

调度器的创建

调度器创建的时候需要指定调度器的名称、是否使用当前线程、调度线程数。调度器创建好后,即可调用调度器的schedule方法向调度器添加调度任务,但此时调度器的调度线程并没有创建,调度器也不会立刻执行这些任务,而是将它们保存到内部的一个任务队列m_tasks中。

调度任务

对于协程调度器来说,协程当然可以作为调度任务,但实际上,函数也应可以,因为函数也是可执行的对象,调度器应当支持使用者将一个函数传入调度器中。这在代码实现上也很简单,只需要将函数包装成协程即可,协程调度器的实现重点还是以协程为基础

在调度器创建之后,我们就可以往调度器里面添加调度任务。虽然我们传递的是一个协程或函数,但是在schedule方法里面会将这个协程或函数包装在这一个调度任务Task对象中

template <typename T>
bool schedule(T v, pid_t thread_id = 0) {
    if (m_is_stopping) return false;
    bool need_tickle = false;
    {
        MutexLock::Lock lock(m_mutex);
        need_tickle = m_tasks.empty();
        m_tasks.push_back(Task(v, thread_id));
    }
    if (need_tickle && m_idle_cnt > 0) {
        tickle();
    }
    return true;
}

template <typename ForwardIterator>
bool schedules(ForwardIterator begin, ForwardIterator end, pid_t thread_id = 0) {
    if (m_is_stopping) return false;
    bool need_tickle = false;
    {
        MutexLock::Lock lock(m_mutex);
        need_tickle = m_tasks.empty();
        while (begin != end) {
            m_tasks.push_back(Task(*begin, thread_id));
            ++begin;
        }
    }
    if (need_tickle && m_idle_cnt > 0) {
        tickle();
    }
    return true;
}

调度器的运行

  • 调用start方法启动调度器。start方法调用后会创建调度线程池,线程数量由初始化时的线程数相关。调度线程一旦创建,就会立刻执行run方法,即线程对应的入口函数。这里比较特殊的是,如果调度器在创建的时候指定创建调度器的线程也作为一个调度线程,那么此线程也将执行run方法
  • run方法负责从调度器的任务队列中取任务执行。如果任务队列空了,那么调度协程会切换到一个idle协程,这个idle协程什么也不做,等有新任务进来时,idle协程才会yield挂起并返回到当前线程的主协程,重新开始下一轮调度
  • 调用schedule方法添加调度任务,这个方法支持传入协程或函数,并且支持一个线程号参数,表示是否将这个协程或函数绑定到一个具体的线程上执行。如果任务队列为空,那么在添加任务之后,要调用一次tickle方法以通知各调度线程有新任务来了
  • 调用stop停止调度器。在调用stop之后会将m_is_stopping设置为true,并且通知各个调度线程开始任务调度。在任务队列为空时,调度线程开始慢慢退出。这里需要注意的是,主线程要等待所有非主调度线程退出之后才能退出,因此在每一个非主调度线程退出时都需要调用一次tickle来唤醒所有处于idel的线程
void Scheduler::run() {
    auto idle_co = std::make_shared<Coroutine>(std::bind(&Scheduler::idle, this));
    s_t_scheduler = shared_from_this();
    Task task;
    while (true) {
        task.reset();
        bool is_active = false;
        bool need_tickle = false;
        {
            MutexLock::Lock lock(m_mutex);
            auto t = m_tasks.begin();
            while (t != m_tasks.end()) {
                if (t->m_thread_id != 0 && t->m_thread_id != Thread::CurThreadId()) {
                    ++t;
                    continue;
                }
                task = *t;
                m_tasks.erase(t);
                if (!task.m_co) {
                    task.reset();
                    continue;
                }
                is_active = true;
                break;
            }
            if (!m_tasks.empty() && m_idle_cnt > 0) {
                need_tickle = true;
            }
        }
        if (need_tickle) {
            tickle();
        }
        if (is_active) {
            if (task.m_co->state() & (Coroutine::State::INIT | Coroutine::State::YIELD)) {
                task.m_co->resume();
            } else {
                TIGER_LOG_E(SYSTEM_LOG) << "[the coroutine is nullptr]";
            }
        } else {
            if (m_is_stopping) {
                if (Thread::CurThreadId() == m_main_thread_id && m_thread_cnt != 0) {
                    idle_co->resume();
                    continue;
                }
                break;
            } else {
                idle_co->resume();
            }
        }
    }
    --m_thread_cnt;
    tickle();
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虎小黑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值