此项目是根据sylar框架实现,是从零开始重写sylar,也是对sylar丰富与完善
项目地址:https://gitee.com/lzhiqiang1999/server-framework
项目介绍:实现了一个基于协程的服务器框架,支持多线程、多协程协同调度;支持以异步处理的方式提高服务器性能;封装了网络相关的模块,包括socket、http、servlet等,支持快速搭建HTTP服务器或WebSokcet服务器。
详细内容:日志模块,使用宏实现流式输出,支持同步日志与异步日志、自定义日志格式、日志级别、多日志分离等功能。线程模块,封装pthread相关方法,封装常用的锁包括(信号量,读写锁,自旋锁等)。IO协程调度模块,基于ucontext_t实现非对称协程模型,以线程池的方式实现多线程,多协程协同调度,同时依赖epoll实现了事件监听机制。定时器模块,使用最小堆管理定时器,配合IO协程调度模块可以完成基于协程的定时任务调度。hook模块,将同步的系统调用封装成异步操作(accept, recv, send等),配合IO协程调度能够极大的提升服务器性能。Http模块,封装了sokcet常用方法,支持http协议解析,客户端实现连接池发送请求,服务器端实现servlet模式处理客户端请求,支持单Reator多线程,多Reator多线程模式的服务器。
/**
* @brief 简单协程调度类,支持添加调度任务以及运行调度任务
*/
class Scheduler {
public:
/**
* @brief 添加协程调度任务
*/
void schedule(sylar::Fiber::ptr task) {
m_tasks.push_back(task);
}
/**
* @brief 执行调度任务
*/
void run() {
Fiber::ptr task;
auto it = m_tasks.begin();
while(it != m_tasks.end()) {
task = *it;
m_tasks.erase(it++);
task->call();
}
}
private:
/// 任务队列
std::list<sylar::Fiber::ptr> m_tasks;
};
// 协程任务
void test_fiber(int i) {
std::cout << "hello world " << i << std::endl;
}
int main() {
/// 初始化当前线程的主协程
Fiber::GetThis();
/// 创建调度器
Scheduler sc;
/// 添加调度任务
for(auto i = 0; i < 10; i++) {
Fiber::ptr fiber(new Fiber(
std::bind(test_fiber, i)
));
sc.schedule(fiber);
}
/// 执行调度任务
sc.run();
return 0;
}
void task2(){
// 任务2
}
void task1() {
// 任务1
Scheduler::GetThis().schedule(task2); // 任务协程中也能添加任务
}
int main(){
// 创建协程调度器,并使用主线程作为调度线程
Scheduler sc(1, true, "name");
sc.schedule(task1);
sc.start();
sc.stop();
return 0;
}
class Scheduler
{
public:
typedef std::shared_ptr<Scheduler> ptr;
typedef Mutex MutexType;
public:
Scheduler(size_t threads = 1, bool use_caller = true, const std::string& name = "");
virtual ~Scheduler();
void schedule(FiberOrCb fc, int thread = -1); //添加任务协程
void schedule(InputIterator begin, InputIterator end);
public:
static Scheduler* GetThis(); // 获取当前协程调度器
static void SetThis(Scheduler* s); // 设置当前协程调度器
static Fiber* GetScheduleFiber(); // 获取当调度协程
private:
struct FiberAndCallBack{ ... };
protected:
void run(); // 协程调度函数
virtual void idle(); // 协程无任务可调度时执行idle协程
virtual bool stopping(); // 返回是否结束
virtual void tickle(); // 唤醒协程
private:
MutexType m_mutex; //锁
std::vector<Thread::ptr> m_threads; //线程池
std::list<FiberAndCallBack> m_fibers; //即将执行和计划执行的协程,由协程完成具体任务
Fiber::ptr m_schedulFiber; //调度协程,只在use_caller = true有效
std::string m_name; //调度器名称
protected:
std::vector<int> m_threadIds; //协程下的线程id数组
int m_rootThread = 0; //主线程id(use_caller = true才使用)
size_t m_threadCount = 0; //线程池中线程数量
bool m_stopping = true; //是否正在停止
bool m_autoStop = false; //是否自动停止
std::atomic<size_t> m_activeThreadCount = { 0 }; //工作线程数量
std::atomic<size_t> m_idleThreadCount = { 0 }; //空闲线程数量
};
FiberAndCallBack
struct FiberAndCallBack
{
Fiber::ptr fiber; // 任务协程
std::function<void()> cb; // 函数
int thread; // 线程id
FiberAndCallBack(Fiber::ptr f, int thr)
:fiber(f),
thread(thr)
{}
FiberAndCallBack(Fiber::ptr* f, int thr)
:thread(thr)
{
fiber.swap(*f);
}
FiberAndCallBack(std::function<void()> f , int thr)
:cb(f),
thread(thr)
{}
FiberAndCallBack(std::function<void()>* f, int thr)
:thread(thr)
{
cb.swap(*f);
}
FiberAndCallBack()
:thread(-1)
{}
void reset()
{
fiber = nullptr;
cb = nullptr;
thread = -1;
}
};
vector<Thread::ptr> m_threads; //线程池
use_caller
来控制。// use_caller: true 使用主线程; false 不使用主线程
Scheduler(size_t threads = 1, bool use_caller = true, const std::string& name = "");
当use_caller
为true时,创建的协程应当在主协程 和 子协程(调度协程)间交换(call、back)。当use_caller
为false时,调度协程 和 调度协程的子协程交换(swapIn、swapOut)。后文还会详细说明。
call/back: 专门负责调度协程和主协程间转换
swapIn/swapOut:专门负责调度协程和任务协程间转换
call swapIn
主协程<--->调度协程<--->任务协程
back swapOut
schedule
方法void task() {
// 任务协程的逻辑处理
}
Fiber::ptr fiber(task)
Scheduler sc;
sc.schedule(fiber);
tickle
方法,告诉其他调度线程有新的任务到来idle
协程,等待新的任务到来// 创建线程池,每个调度线程都注册了一个调度器的执行函数
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());//这里需要注意,当new Thread的时候,我们wait了一下,等线程执行函数初始化完再notify,这样就保证了能拿到此处的线程id;
}
Fiber::ptr idle_fiber(new Fiber(std::bind(&Scheduler::idle, this)));//闲置协程
Fiber::ptr cb_fiber;//使用回调函数的协程
while(true) {
auto it = m_fibers.begin();
while (it != m_fibers.end())
{
//当前协程任务(FiberAndThread)中设置的线程id != 当前线程id(我们指定了每个协程任务(FiberAndThread)应该在哪里跑)
if (it->thread != -1 && it->thread != johnsonli::getThreadId())
{
++it;
tickle_me = true;
continue;
}
DO_ASSERT(it->fiber || it->cb);
//协程任务(FiberAndThread)中的协程目前正在EXEC状态,不执行
if (it->fiber && it->fiber->getState() == Fiber::EXEC) {
++it;
continue;
}
//找到了一个可以执行的协程任务(FiberAndThread)
fc = *it;
m_fibers.erase(it++);//获取当前协程任务(FiberAndThread)后,从协程队列中移除
++m_activeThreadCount;//增加一个线程执行
is_active = true;//当前线程存活
break;
}
tickle_me |= (it != m_fibers.end());//如果还没有遍历完,就可以通知调度器有任务
if (tickle_me) { tickle(); //通知调度器有任务 }
if (fc.fiber && (fc.fiber->getState() != Fiber::TERM
&& fc.fiber->getState() != Fiber::EXCEPT)) {
fc.fiber->swapIn(); //执行任务协程
....
}else if (fc.cb) { //协程任务有回调函数
cb_fiber.reset(new Fiber(fc.cb));
fc.reset();
cb_fiber->swapIn(); //执行任务协程
}
else {
idle.swapIn(); //执行idle协程
}
}
void Scheduler::idle() {
//如果没结束,就切换成HOLD(暂停)状态,这样就不会退出当前线程
//这里要使用while,因为下一次swapIn,还会再判断一次
while(!stopping()) {
Fiber::YieldToHoldBySwap();
}
}
void Scheduler::stop() {
m_autoStop = true;
//调度协程!=null && 调度协程状态=TERM(已经完成) | INIT(还未开始) && 当前线程池为null
if (m_schedulFiber
&& (m_schedulFiber->getState() == Fiber::TERM || m_schedulFiber->getState() == Fiber::EXCEPT || m_schedulFiber->getState() == Fiber::INIT)
&& m_threadCount == 0) {
LOG_INFO(g_logger) << this << " stopped";
m_stopping = true;
if (stopping()) return;
}
//use_caller,使用主线程时,只在主线程中stop
if (m_rootThread != -1) //m_scheduleFiber
{
DO_ASSERT(GetThis() == this);
}
else
{
//不使用主线程,任意线程都可以stop
DO_ASSERT(GetThis() != this);
}
m_stopping = true;
//唤醒线程池中的线程
for (size_t i = 0; i < m_threadCount; ++i) {
tickle();
}
//唤醒调度协程
if (m_schedulFiber) tickle();
//调度协程 != null 有m_scheduleFiber 一定是在主线程
if (m_schedulFiber) {
if (!stopping()) m_schedulFiber->call();
}
std::vector<Thread::ptr> thrs;
{
MutexType::Lock lock(m_mutex);
thrs.swap(m_threads);
}
//让其他线程先结束,留一个线程来回收
//如果在主线程stop,可能主线程先结束,然后sc就释放,但是子线程还在跑,使用sc,会core dump
for (auto& i : thrs) {
i->join();
}
}
use_calle
r参数,表示是否使用主线程作为调度线程。在使用主线程的情况下,线程数自动减一,并且调度器内部会初始化一个属于主线程的调度协程并保存起来(比如,在main函数中创建的调度器,如果use_caller
为true,那调度器会初始化一个属于main函数线程的调度协程)。schedule
方法向调度器添加调度任务,但此时调度器并不会立刻执行这些任务,而是将它们保存到内部的一个任务协程队列中。use_caller
确定。调度线程一旦创建,就会立刻从任务队列里取任务执行。比较特殊的一点是,如果初始化时指定线程数为1且use_caller
为true,那么start方法什么也不做,因为不需要创建新线程用于调度。并且,由于没有创建新的调度线程,那只能由主线程的调度协程来负责调度协程,而主线程的调度协程的执行时机与start方法并不在同一个地方,它只在stop中执行。run
方法。调度协程负责从调度器的协程任务队列中取任务执行。取出的任务即子协程,这里调度协程和子协程的切换模型即为前一章介绍的非对称模型,每个子协程执行完后都必须返回调度协程,由调度协程重新从协程任务队列中取新的任务并执行。如果任务队列空了,那么调度协程会切换到一个idle协程,这个idle协程什么也不做,等有新任务进来时,idle协程才会退出并回到调度协程,重新开始下一轮调度。
GetThis()
方法获取到当前调度器,再通过schedule
方法继续添加新的任务,这就变相实现了在子协程中创建并运行新的子协程的功能。use_caller
为false的情况,这种情况下,由于没有使用caller线程进行调度,那么只需要简单地等各个调度线程的调度协程退出就行了。如果use_caller
为true,表示caller线程也要参于调度,这时,调度器初始化时记录的属于caller线程的调度协程就要起作用了,在调度器停止前,应该让这个caller线程的调度协程也运行一次,让caller线程完成调度工作后再退出。如果调度器只使用了caller线程进行调度,那么所有的调度任务要在调度器停止时才会被调度。use_caller
为false,应额外创建一个线程进行协程调度、main函数线程不参与调度的情况。
call/back: 专门负责调度协程和主协程间转换
swapIn/swapOut:专门负责调度协程和任务协程间转换
call swapIn
主协程<--->调度协程<--->任务协程
back swapOut