依赖项:
cmake
boost
yaml-cpp
可以通过以下命令安装:
sudo apt-get install cmake libboost-all-dev libyaml-cpp-dev
支持流式日志风格写日志和格式化风格写日志,支持日志格式自定义,日志级别,多日志分离等等功能。
支持自由配置日期时间,累计运行毫秒数,线程id,线程名称,协程id,日志线别,日志名称,文件名,行号。
工具接口与工具类,功能宏定义。包括获取时间,日期时间格式转换,栈回溯,文件系统操作接口,类型转换接口,以及SYLAR_ASSERT宏。详细接口参考util.h,macro.h。
提供管理环境变量管理功能,包括系统环境变量,自定义环境变量,命令行参数,帮助信息,exe名称与程序路径相关的信息。环境变量全部以key-value的形式进行存储,key和value都是字符串格式。提供add/get/has/del接口用于操作自定义环境变量和命令行选项与参数,提供setEnv/getEnv用于操作系统环境变量,提供addHelp/removeHelp/printHelp用于操作帮忙信息,提供getExe/getCwd用于获取程序名称及程序路径,提供getAbsolutePath/getAbsoluteWorkPath/getConfigPath用于获取路径相关的信息。
采用约定优于配置的思想。定义即可使用。支持变更通知功能。使用YAML文件做为配置内容,配置名称大小写不敏感。支持级别格式的数据类型,支持STL容器(vector,list,set,map等等),支持自定义类型的支持(需要实现序列化和反序列化方法)。
使用方式如下:
static sylar::ConfigVar<int>::ptr g_tcp_connect_timeout =
sylar::Config::Lookup("tcp.connect.timeout", 5000, "tcp connect timeout");
定义了一个tcp连接超时参数,可以直接使用g_tcp_connect_timeout->getValue()
获取参数的值,当配置修改重新加载,该值自动更新(并触发对应的值更新回调函数),上述配置格式如下:
tcp:
connect:
timeout: 10000
线程模块,封装了pthread里面的一些常用功能,Thread,Semaphore,Mutex,RWMutex,Spinlock等对象,可以方便开发中对线程日常使用。为什么不适用c++11里面的thread 本框架是使用C++11开发,不使用thread,是因为thread其实也是基于pthread实现的。并且C++11里面没有提供读写互斥量,RWMutex,Spinlock等,在高并发场景,这些对象是经常需要用到的。所以选择了自己封装pthread。
协程:用户态的线程,相当于线程中的线程,更轻量级。后续配置socket hook,可以把复杂的异步调用,封装成同步操作。降低业务逻辑的编写复杂度。 目前该协程是基于ucontext_t来实现的,后续将支持采用boost.context里面的fcontext_t的方式实现。
协程原语:
resume
:恢复,使协程进入执行状态
yield
: 让出,协程让出执行权
yield和resume是同步的,也就是,一个协程的resume必然对应另一个协程的yield,反之亦然,并且,一条线程同一时间只能有一个协程是执行状态。
协程调度器,管理协程的调度,内部实现为一个线程池,支持协程在多线程中切换,也可以指定协程在固定的线程中执行。是一个N-M的协程调度模型,N个线程,M个协程。重复利用每一个线程。限制一个线程只能有一个协程调度器
继承自协程调度器,封装了epoll(Linux),支持注册socket fd事件回调。只支持读写事件。IO协程调度器解决了协程调度器在idle情况下CPU占用率高的问题,当调度器idle时,调度器会阻塞在epoll_wait上,当IO事件发生或添加了新调度任务时再返回。通过一对pipe fd来实现通知调度协程有新任务。
在IO协程调度器之上再增加定时器调度功能,也就是在指定超时时间结束之后执行回调函数。定时的实现机制是idle协程的epoll_wait超时,大体思路是创建定时器时指定超时时间和回调函数,然后以当前时间加上超时时间计算出超时的绝对时间点,然后所有的定时器按这个超时时间点排序,从最早的时间点开始取出超时时间作为idle协程的epoll_wait超时时间,epoll_wait超时结束时把所有已超时的定时器收集起来,执行它们的回调函数。
hook系统底层和socket相关的API,socket io相关的API,以及sleep系列的API。hook的开启控制是线程粒度的。可以自由选择。通过hook模块,可以使一些不具异步功能的API,展现出异步的性能。如(mysql)
hook实际就是把系统提供的api再进行一层封装,以便于在执行真正的系统调用之前进行一些操作。hook的目的是把socket io相关的api都转成异步,以便于提高性能。hook和io调度是密切相关的,如果不使用IO协程调度器,那hook没有任何意义。考虑IOManager要调度以下协程:
协程1:sleep(2) 睡眠两秒后返回
协程2:在scoket fd1上send 100k数据
协程3:在socket fd2上recv直到数据接收成功
在未hook的情况下,IOManager要调度上面的协程,流程是下面这样的:
- 调度协程1,协程阻塞在sleep上,等2秒后返回,这两秒内调度器是被协程1占用的,其他协程无法被调度。
- 调度协徎2,协程阻塞send 100k数据上,这个操作一般问题不大,因为send数据无论如何都要占用时间,但如果fd迟迟不可写,那send会阻塞直到套接字可写,同样,在阻塞期间,调度器也无法调度其他协程。
- 调度协程3,协程阻塞在recv上,这个操作要直到recv超时或是有数据时才返回,期间调度器也无法调度其他协程
上面的调度流程最终总结起来就是,协程只能按顺序调度,一旦有一个协程阻塞住了,那整个调度器也就阻塞住了,其他的协程都无法执行。像这种一条路走到黑的方式其实并不是完全不可避免,以sleep为例,调度器完全可以在检测到协程sleep后,将协程yield以让出执行权,同时设置一个定时器,2秒后再将协程重新resume,这样,调度器就可以在这2秒期间调度其他的任务,同时还可以顺利的实现sleep 2秒后再执行的效果。send/recv与此类似,在完全实现hook后,IOManager的执行流程将变成下面的方式:
- 调度协程1,检测到协程sleep,那么先添加一个2秒的定时器,回调函数是在调度器上继续调度本协程,接着协程yield,等定时器超时。
- 因为上一步协程1已经yield了,所以协徎2并不需要等2秒后才可以执行,而是立刻可以执行。同样,调度器检测到协程send,由于不知道fd是不是马上可写,所以先在IOManager上给fd注册一个写事件,回调函数是让当前协程resume并执行实际的send操作,然后当前协程yield,等可写事件发生。
- 上一步协徎2也yield了,可以马上调度协程3。协程3与协程2类似,也是给fd注册一个读事件,回调函数是让当前协程resume并继续recv,然后本协程yield,等事件发生。
- 等2秒超时后,执行定时器回调函数,将协程1 resume以便继续执行。
- 等协程2的fd可写,一旦可写,调用写事件回调函数将协程2 resume以便继续执行send。
- 等协程3的fd可读,一旦可读,调用回调函数将协程3 resume以便继续执行recv。
上面的4、5、6步都是异常的,系统并不会阻塞,IOManager仍然可以调度其他的任务,只在相关的事件发生后,再继续执行对应的任务即可。并且,由于hook的函数对调用方是不可知的,调用方也不需要知道hook的细节,所以对调用方也很方便,只需要以同步的方式编写代码,实现的效果却是异常执行的,效率很高。