协程调度器
在学习协程调度器之前必须掌握协程
当你有很多协程时,如何把这些协程都消耗掉,这就是设计协程调度器的目的。在协程模块中,对于每个协程,都需要使用者手动调用协程的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();
}