sylar作者在本站的地址为 这里,也可以查看 作者主页,也有视频教程可以 点击这里。此外,也可以看一下赵路强大佬的关于sylar协程模块的博客 点击这里,我本人在阅读sylar源码的时候也是参考了赵路强大佬的解析 可以点击这里。
各位看官也可以加我的qq和我讨论2511010742
在上期博客中,实现了两个协程之间的切换,但是单独的切换两个协程并不能做到什么高效率的事情,在一个子协程中不可以直接调用另一个子协程,必须先切换回主协程。在本节中,解决了这个问题,可以通过在子协程中向调度器添加另一个子协程,这样的话就变相的实现了在子协程中添加子协程的操作。
当创建了很多协程之后,将这些协程按照一定规则消耗掉就称为协程调度,此外还实现了添加协程的功能。简而言之,协程调度器创建N个线程,利用这N个线程去运行M个协程,实现了一个N-M协程调度器,协程可以在线程中切换,也可以指定线程去运行协程。
对于协程调度器,不仅可以调度协程,也应该可以直接调度函数。sylar的协程调度器支持多线程,并且main函数所在的线程也可以参加到协程调度中来,支持添加协程或者函数作为调度的对象。协程调度器在初始化时会传入一个use_caller布尔值,由这个布尔变量决定是否将main函数所在的线程也添加到协程调度中去。如果使用的话,就可以少创建一个线程,减少了系统的开销。
在调度器Scheduler中,可以使用scheduler方法添加任务到调度器中,将这些任务保存到内部的一个任务队列中,之后调用start方法启动协程调度器,在start方法中会创建一个线程池,如果use_caller为true的话就创建N-1个线程,反之创建N个线程。每个线程都会创建一个调度协程,这个协程专门用来进行任务调度,当use_caller为true时,main函数所在的线程会自行创建调度协程。
每个线程的调度协程都会运行各自相应的run方法,在run方法中,主要负责从调度器的任务队列中取出任务并执行,这里取出的任务可以看作调度协程的子协程,他们俩的调度关系就是上一节中讲述的简单切换方式,由调度协程切换到任务协程,执行完之后再切换到调度协程,继续取任务执行,每个子任务协程都必须要切换回调度协程。如果任务队列为空,那么就会进入一个idle空闲协程中,目前这个空闲协程什么也不做,进入空闲协程之后就会退出,此时还是没有新任务,就又进入空闲协程,然后又退出,以此往复,直到有新任务到来才会取执行新的任务调度。
在main函数线程中,第一个创建的主协程并不是调度协程,主协程的子协程才是真正的调度协程,因为main线程中,天生就有一个主协程。而在其他线程中,调度协程就是线程的主协程(这是sylar协程调度模块中很难理解的地方)。非main线程中,主协程就是调度协程,任务调度则是主协程直接切换到任务子协程运行,再切换回主协程,以此往复。而在main线程中,程序一开始就运行在main线程的主协程中,所以main线程的主协程会先切换到调度协程,再由调度协程去调度任务子协程,等到没有任务时,并且要退出调度器时,调度协程才会切换回主协程,也就是回到main函数中继续运行直到程序结束。
sylar的协程调度模块通过scheduler方法添加新的任务,它还可以指定一个具体的线程去执行新添加的任务,在scheduler方法中,如果目前任务队列已经为空了,那么添加任务之后会调用一次tickle方法,这个方法会通知各个线程有新任务到来了。在执行任务的途中也可以调用scheduler来添加任务,通过GetThis()方法获取当前调度器,再通过调用调度器的scheduler方法来添加任务。
关于协程调度器的停止也要分为两种情况,即main函数线程有没有参与调度,如果没有的话,在调用stop方法之后,其他线程如果没有任务了,会先进入一次idle空闲协程,然后再退出,此时idle协程的状态就是已完成状态,这个线程就优雅的退出了。如果main函数线程参与了协程调度的话,再stop方法中会单独的执行一次main线程的调度协程,在没有任务之后就会退出到main函数线程的主协程,之后整个调度器就优雅的退出了。因为main函数线程的调度协程是在stop方法中去执行的,所以在只有main线程参与调度的情况下,这些需要调度的任务会在调度器停止时才会被调度。
当然,以上是sylar设计的协程调度器,我们在使用时也可以进行相应的优化,可以适当的改进。
下图为不使用main线程调度的简单流程,创建了一个额外的调度线程。
下图为使用main线程的流程图。使用main函数所在的线程,而且还额外创建了一个线程,这样就有两个线程参与调度。此图中的m_root_fiber就是main线程的调度协程。
我会尽可能地将sylar源码的注释写明白。首先是协程调度器Sheduler类的定义
namespace sylar {
/**
* @brief 协程调度器
* @details 封装的是N-M的协程调度器
* 内部有一个线程池,支持协程在线程池里面切换
*/
class Scheduler {
public:
typedef std::shared_ptr<Scheduler> ptr;
typedef Mutex MutexType;
/**
* @brief 构造函数
* @param[in] threads 线程数量
* @param[in] use_caller 是否使用当前调用线程
* @param[in] name 协程调度器名称
*/
Scheduler(size_t threads = 1, bool use_caller = true, const std::string& name = "");
virtual ~Scheduler();
// get scheduler name
const std::string& getName() const { return m_name;}
// get curr scheduler p
static Scheduler* GetThis();
// 返回当前协程调度器的调度携程(不一定是主协程)
static Fiber* GetMainFiber();
// 启动协程调度器
void start();
// 停止协程调度器
void stop();
/**
* @brief 调度协程
* @param[in] fc 协程或函数
* @param[in] thread 协程执行的线程id,-1标识任意线程
*/
template<class FiberOrCb>
void schedule(FiberOrCb fc, int thread = -1) {
bool need_tickle = false;
{
MutexType::Lock lock(m_mutex);
need_tickle = scheduleNoLock(fc, thread);
}
if(need_tickle) {
std::cout << "[INFO] add fc in here, tickle other thread" << std::endl;
tickle();
//std::cout << "[INFO] tell over" << std::endl;
}
}
/**
* @brief 批量调度协程
* @param[in] begin 协程数组的开始
* @param[in] end 协程数组的结束
*/
template<class InputIterator>
void schedule(InputIterator begin, InputIterator end) {
bool need_tickle = false;
{
MutexType::Lock lock(m_mutex);
while(begin != end) {
need_tickle = scheduleNoLock(&*begin, -1) || need_tickle;
++begin;
}
}
if(need_tickle) {
tickle();
}
}
// void switchTo(int thread = -1);
// std::ostream& dump(std::ostream& os);
protected:
// 通知调度器有任务
virtual void tickle();
// 协程调度函数
void run();
// 返回是否可以停止
virtual bool stopping();
// 协程无任务时可调度执行idle协程
virtual void idle();
// 设置当前的协程调度器
void setThis();
// 是否有空闲协程
bool hasIdleThreads() { return m_idleThreadCount > 0;}
private:
// 协程调度启动,添加任务
template<class FiberOrCb>
bool scheduleNoLock(FiberOrCb fc, int thread) {
bool need_tickle = m_fibers.empty();
FiberAndThread ft(fc, thread);
if(ft.fiber || ft.cb) {
m_fibers.push_back(ft);
}
return need_tickle;
}
/**
* @brief 协程/函数/线程组
*/
struct FiberAndThread {
// 协程
Fiber::ptr fiber;
// 协程执行函数
std::function<void()> cb;
// 线程id
int thread;
FiberAndThread(Fiber::ptr f, int thr)
:fiber(f), thread(thr) {}
FiberAndThread(Fiber::ptr* f, int thr)
:thread(thr) {
fiber.swap(*f);
}
FiberAndThread(std::function<void()> f, int thr)
:cb(f), thread(thr) {}
FiberAndThread(std::function<void()>* f, int thr)
:thread(thr) {
cb.swap(*f);
}
FiberAndThread()
:thread(-1) {}
// 重置数据
void reset() {
fiber = nullptr;
cb = nullptr;
thread = -1;
}
};
private:
// Mutex
MutexType m_mutex;
// 线程池
std::vector<Thread::ptr> m_threads;
// 待执行的协程队列
std::list<FiberAndThread> m_fibers;
// use_caller为真时有效,调度协程
Fiber::ptr m_rootFiber;
// 协程调度器名称
std::string m_name;
protected:
// 协程下的线程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_stopping = true;
// 是否自动停止
bool m_autoStop = false;
// 主线程id
int m_rootThread = 0;
};
接下来是方法实现
namespace sylar {
// 当前线程的调度器,同一个调度器控制下的所有线程的调度器都是相同的
static thread_local Scheduler* t_scheduler = nullptr;
// 当前线程的调度协程,每一个线程都有一个调度协程,也包括main函数线程
static thread_local Fiber* t_scheduler_fiber = nullptr;
/*
t_scheduler_fiber保存当前线程的的调度协程
Fiber::t_fiber保存当前正在运行的协程
Fiber::t_thread_fiber保存的是当前线程的主协程
*/
Scheduler::Scheduler(size_t threads, bool use_caller, const std::string& name)
:m_name(name) {
// SYLAR_ASSERT(threads > 0);
if(use_caller) {
sylar::Fiber::GetThis(); // 创建main函数线程第一个协程
--threads; // 使用main线程参与调度,就可以少创建一个额外的调度线程
// SYLAR_ASSERT(GetThis() == nullptr);
t_scheduler = this;
// 重新创建主协程,将run方法与m_rootFiber绑定
// 主协程在上面已经通过GetThis方法创建过了,所以这里说是重新创建
// 其实就是main函数线程的调度协程,
// 因为其他每个调度线程都是他们的主协程作为调度协程的
// 所以才取了m_rootFiber来命名main线程的调度协程,其实他并不是这个线程的主协程。
m_rootFiber.reset(new Fiber(std::bind(&Scheduler::run, this), 0, true));
// 将main线程的m_rootFiber协程赋给main线程的调度协程
// 因为t_scheduler_fiber保存每个线程的调度协程,所以也将m_rootFiber赋给它
t_scheduler_fiber = m_rootFiber.get();
// 将主线程放入线程池管理
m_threadIds.push_back(m_rootThread);
} else {
m_rootThread = -1;
}
// 如果不使用main线程的话,那么需要创建的线程数不变
m_threadCount = threads;
}
Scheduler::~Scheduler() {
// SYLAR_ASSERT(m_stopping);
if(GetThis() == this) {
t_scheduler = nullptr; // 将调度器置空
}
}
// 返回调度器指针
Scheduler* Scheduler::GetThis() {
return t_scheduler;
}
// 返回当前线程的调度协程
Fiber* Scheduler::GetMainFiber() {
return t_scheduler_fiber;
}
void Scheduler::start() {
std::cout << "[INFO] Scheduler start!" << std::endl;
MutexType::Lock lock(m_mutex);
if(!m_stopping) {
return;
}
m_stopping = false;
// SYLAR_ASSERT(m_threads.empty());
// 设置线程池大小
m_threads.resize(m_threadCount);
for(size_t i = 0; i < m_threadCount; ++i) {
// 重置每个线程的调度器执行函数
// 将run方法绑定为这些线程的入口函数
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());
std::cout << "[INFO] create thread No." << i << std::endl;
}
lock.unlock();
}
void Scheduler::stop() {
std::cout << "[INFO] in stop func" << std::endl;
m_autoStop = true;
if(m_rootFiber
&& m_threadCount == 0
&& (m_rootFiber->getState() == Fiber::TERM
|| m_rootFiber->getState() == Fiber::INIT)) {
std::cout << "[DEBUG] " << this << " stopped" << std::endl;
m_stopping = true;
if(stopping()) { // 留给子类实现
return;
}
}
if(m_rootThread != -1) {
// SYLAR_ASSERT(GetThis() == this);
} else {
// SYLAR_ASSERT(GetThis() != this);
}
m_stopping = true;
for(size_t i = 0; i < m_threadCount; i++) {
std::cout << "[INFO] tickle other thread stop" << std::endl;
tickle();
}
if(m_rootFiber) {
std::cout << "[INFO] m_rootFiber tickle" << std::endl;
tickle();
}
if(m_rootFiber) {
if(!stopping()) {
std::cout << "[INFO] m_rootFiber run" << std::endl;
m_rootFiber->call();
/*
此处为main线程的调度协程启动入口,这里是调用的是call方法
它执行完之后会返回到main线程的主协程上
目前运行到这一行就是处于main的主协程。这里的主协程并不是调度协程
##########################
而其他线程调用的是swapin方法,它会返回到对应线程的主协程,也就是调度协程
*/
}
}
std::vector<Thread::ptr> thrs;
{
MutexType::Lock lock(m_mutex);
thrs.swap(m_threads);
}
for(auto& i : thrs) {
i->join();
}
std::cout << "[INFO] stop func over" << std::endl;
}
// 设置当前调度器
void Scheduler::setThis() {
t_scheduler = this;
}
void Scheduler::run() {
std::cout << "[DEBUG] " << m_name << " run" << std::endl;
setThis();
//std::cout << "[DEBUG] " << std::endl;
// 如果当前线程不是主线程的话
if(syscall(SYS_gettid) != m_rootThread) {
// 那么当前线程的调度协程就是执行run方法的协程
t_scheduler_fiber = Fiber::GetThis().get();
}
// 创建idle空闲协程
Fiber::ptr idle_fiber(new Fiber(std::bind(&Scheduler::idle, this)));
std::cout << "[DEBUG] create idle fiber" << std::endl;
// 创建回调协程
Fiber::ptr cb_fiber;
// 创建一个协程/函数/线程组
FiberAndThread ft;
// 接下来会一直循环搜索需要调度的任务
while(true) {
// 将ft其中的协程、回调置空
ft.reset();
bool tickle_me = false; // 是否提醒其他线程进行任务调度
bool is_active = false;
{
MutexType::Lock lock(m_mutex);
auto it = m_fibers.begin();
while(it != m_fibers.end()) { // 从协程队列的开始到结束
// 说明指定了线程去执行,但不是当前线程,标记一下通知其他线程去调度
// 然后跳过,继续查看下一个任务
if(it->thread != -1 && it->thread != syscall(SYS_gettid)) {
++it;
tickle_me = true;
continue;
}
// SYLAR_ASSERT(it->fiber || it->cb);
// 这个协程任务处于执行中状态,不去管他
if(it->fiber && it->fiber->getState() == Fiber::EXEC) {
++it;
continue;
}
// 满足条件后,我就去处理它
ft = *it;
m_fibers.erase(it++); // 将其从队列中移除
++m_activeThreadCount;
is_active = true;
break;
}
// 当前线程取走一个任务后,还有任务剩余,那么就提醒其他线程去处理
tickle_me |= (it != m_fibers.end());
}
if(tickle_me) {
std::cout << "[INFO] tell other thread, the fc are here" << std::endl;
tickle();
}
// ft这个任务的状态不等于结束也不等于异常
if(ft.fiber && (ft.fiber->getState() != Fiber::TERM
&& ft.fiber->getState() != Fiber::EXCEPT)) {
ft.fiber->swapIn(); // 执行此协程
--m_activeThreadCount;
// 如果此协程状态又被设置为READY状态,就重新将其加入调度队列
// 这说明ft任务半路推出了
if(ft.fiber->getState() == Fiber::READY) {
schedule(ft.fiber);
} else if(ft.fiber->getState() != Fiber::TERM
&& ft.fiber->getState() != Fiber::EXCEPT) {
ft.fiber->m_state = Fiber::HOLD;
// 如果ft状态不等于结束也不等于异常也不等于就绪的话
// 就将其设置为暂停
}
ft.reset(); // 清除ft
} else if(ft.cb) { // 如果ft是一个callback回调函数的话
if(cb_fiber) { // 如果cb以初始化的话
cb_fiber->reset(ft.cb); // 普通指针
} else { // 没初始化
cb_fiber.reset(new Fiber(ft.cb)); // 智能指针
std::cout << "[INFO] it is cb_fiber.reset(new Fiber(ft.cb))" << std::endl;
}
ft.reset(); // 清除ft
cb_fiber->swapIn(); // 去执行cb
--m_activeThreadCount;
// 执行到半路退出了
if(cb_fiber->getState() == Fiber::READY) {
schedule(cb_fiber); // 重新加载
cb_fiber.reset(); // 清除cb
} else if(cb_fiber->getState() == Fiber::EXCEPT
|| cb_fiber->getState() == Fiber::TERM) {
cb_fiber->reset(nullptr); // 重置cb
} else {
cb_fiber->m_state = Fiber::HOLD;
cb_fiber.reset(); // 智能指针
}
} else { // 进入这里的话,说明任务队列为空,开始idle协程
if(is_active) {
--m_activeThreadCount;
continue;
}
// 如果调度器没任务的话,idle协程会不停的In和Out,
// 在idle协程中什么也没做,具体业务留给子类
// 如果idle协程结束了,说明一定是调度器停止了
if(idle_fiber->getState() == Fiber::TERM) {
std::cout << "[DEBUG] idle fiber term" << std::endl;
break;
}
++m_idleThreadCount;
idle_fiber->swapIn(); // 执行idle协程
--m_idleThreadCount;
if(idle_fiber->getState() != Fiber::TERM
&& idle_fiber->getState() != Fiber::EXCEPT) {
idle_fiber->m_state = Fiber::HOLD;
}
}
}
}
void Scheduler::tickle() {
std::cout << "[INFO] tickle" << std::endl;
}
bool Scheduler::stopping() {
MutexType::Lock lock(m_mutex);
return m_autoStop && m_stopping
&& m_fibers.empty() && m_activeThreadCount == 0;
}
void Scheduler::idle() {
std::cout << "[INFO] idle" << std::endl;
while(!stopping()) {
sylar::Fiber::YieldToHold();
}
}
// void Scheduler::switchTo(int thread) {
// }
// std::ostream& Scheduler::dump(std::ostream& os) {
// return os;
// }
// SchedulerSwitcher::SchedulerSwitcher(Scheduler* target) {
// m_caller = Scheduler::GetThis();
// if(target) {
// target->switchTo();
// }
// }
// SchedulerSwitcher::~SchedulerSwitcher() {
// if(m_caller) {
// m_caller->switchTo();
// }
// }
}
我在程序中加入了大量的调试信息方便调试,首先我们测试一下需要一个调度线程并且main线程参与其中的情况,其实就是只使用main线程来进行任务调度。
void test_fiber() {
static int s_count = 2;
std::cout << "[INFO] test in fiber s_count = " << s_count << std::endl;
// sleep(1);
// if(-- s_count >= 0) {
// sylar::Scheduler::GetThis()->schedule(&test_fiber, syscall(SYS_gettid));
// }
std::cout << "[INFO] test in fiber over, s_count = " << s_count << std::endl;
}
void test() {
std::cout << "[INFO] test in fiber over, s_count = " << 8 << std::endl;
}
int main(int argc, char** argv) {
std::cout << "[INFO] main" << std::endl;
sylar::Scheduler sc(1, true, "test_Scheduler");
sc.start();
sleep(2);
std::cout << "[INFO] schedule" << std::endl;
sc.schedule(&test);
sc.schedule(&test_fiber);
sc.stop();
//for(;;){}
std::cout << "[INFO] schedule over" << std::endl;
return 0;
}
在这个测试代码中我们添加了两个任务函数,其中一个会输出test in fiber over, s_count = 8
另一个会输出
test in fiber s_count = 2
test in fiber over, s_count = 2
下面我们看一下执行结果
这里可以看到没有打印after pthread_create这一条日志,这条日志添加到线程类的构造函数中去了,说明并没有额外创建线程。在scheduler下面打印了两行提醒其他线程有任务加入的日志,然后进入stop函数,然后m_rootFiber开始执行,创建空闲协程,创建用来执行任务的cb_fiber协程,然后就是那两个任务的执行,执行完之后,会进入idle协程,然后idle协程执行完毕,就会退出到主协程,然后stop结束,最后退出程序。
接下来我们不适用main线程,将use_caller设为false再从新执行以上测试程序。
int main(int argc, char** argv) {
std::cout << "[INFO] main" << std::endl;
sylar::Scheduler sc(1, false, "test_Scheduler");
sc.start();
sleep(2);
std::cout << "[INFO] schedule" << std::endl;
sc.schedule(&test);
sc.schedule(&test_fiber);
sc.stop();
//for(;;){}
std::cout << "[INFO] schedule over" << std::endl;
return 0;
}
运行结果如下
可以看到打印了after pthread_create这一条日志,说明额外创建了一个线程,也和上面一样将这两个任务执行完毕。接下来我们将test任务指定为main线程去执行,但是我们并没有让main线程参与调度,来看一下结果
int main(int argc, char** argv) {
std::cout << "[INFO] main" << std::endl;
sylar::Scheduler sc(1, false, "test_Scheduler");
sc.start();
sleep(2);
std::cout << "[INFO] schedule" << std::endl;
sc.schedule(&test, syscall(SYS_gettid));
sc.schedule(&test_fiber);
sc.stop();
//for(;;){}
std::cout << "[INFO] schedule over" << std::endl;
return 0;
}
结果如下
可以看到,这里一直再重复打印一样的日志,这说明有任务需要执行,但指定了main线程,实际工作的线程并不是main线程所以它就会一直提醒其他线程来处理这个业务,但是可怜的他并不知道,此时以没人会去搭理他,他便一直重复这一件事情。
接下来我们将使用main线程,并且额外再创建一个线程,在实现一下上面的任务。
int main(int argc, char** argv) {
std::cout << "[INFO] main" << std::endl;
sylar::Scheduler sc(2, true, "test_Scheduler");
sc.start();
sleep(2);
std::cout << "[INFO] schedule" << std::endl;
sc.schedule(&test, syscall(SYS_gettid));
sc.schedule(&test_fiber);
sc.stop();
//for(;;){}
std::cout << "[INFO] schedule over" << std::endl;
return 0;
}
结果如下
这次打印的调试信息有一点点多,哈哈,我写的调试信息太冗余了,可以看出来,程序正常执行了。
调度线程会在启动了stop方法后在没有任务时自动退出,下面我们不调用stop函数,不使用main线程,不指定任务的执行线程,并在main函数最后循环等待,看一下执行效果
int main(int argc, char** argv) {
std::cout << "[INFO] main" << std::endl;
sylar::Scheduler sc(1, false, "test_Scheduler");
sc.start();
sleep(2);
std::cout << "[INFO] schedule" << std::endl;
sc.schedule(&test);
sc.schedule(&test_fiber);
// sc.stop();
for(;;){}
std::cout << "[INFO] schedule over" << std::endl;
return 0;
}
效果图如下
可以看到,在执行完那两个任务之后,没有打印任何信息了,main线程在这里一直循环等待,而那个调度线程则是反复在idle与调度协程之间左右互搏,因为没有停止信号,所以idle并不会执行结束,只会在半路退回到调度协程,然后再进入idle协程。
具体sylar协程调度器的流程讲解,代码讲解,结果调试都上上面给出。还有一些情况的调试信息我没有给出,比如只是用main线程调度,但是不执行stop函数,这样是没法启动调度的。我们可以根据自己的具体需求对这个调度器进行个性化修改。目前只能添加无参数无返回值的函数,在后面我想看看能不能设置成向golang一样的协程库。我能力有限,希望我可以做到。加油奥里给!