本项目来源: https://github.com/sylar-yin/sylar
- WSL2-- Ubuntu20 (GCC 9.4)
- Clion 2023
- CMake 3.16.3
sudo apt install libboost-all-dev
sudo apt-get install libyaml-cpp-dev
sudo apt install ragel
bin ----- 二进制
build ---- 中间文件路径
cmake----cmake函数文件
CMakeList.txt ---- cmake的定义文件
lib ---- 库的输出路径
Makefile
sylar ---- 源码路径
tests----测试代码
日志系统设计类图
使用
sylar::Logger::ptr logger(new sylar::Logger);
logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutLogAppender));
sylar::FileLogAppender::ptr file_appender(new sylar::FileLogAppender("./log.txt"));
sylar::LoggerFormatter::ptr fmt(new sylar::LoggerFormatter("%d%T%m%n"));
file_appender->setFormatter(fmt);
file_appender->setLevel(sylar::LogLevel::ERROR);
// logger->addAppender(file_appender);
// sylar::LogEvent::ptr event(new sylar::LogEvent(logger,
// sylar::LogLevel::DEBUG,
// __FILE__, __LINE__,
// 0, sylar::GetThreadId(),
// sylar::GetFiberId(), time(0)));
// event->getSS() << "Hello ";
// logger->log(sylar::LogLevel::DEBUG, event);
SYLAR_LOG_INFO(logger) << "test info";
SYLAR_LOG_ERROR(logger) << "test error";
SYLAR_LOG_FMT_ERROR(logger, "test fmt error %s", "aa");
auto l = sylar::LoggerMgr::GetInstance()->getLogger("xx");
SYLAR_LOG_INFO(l) << "xxx";
Logger(定义日志类别)
|
|-----Formatter(日志格式)
|
Appender(日志输出地方)
Logger 默认构造函数生成的logger
name: root
level: DEBUG
format: %d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n
使用的第三方库
class ConfigVarBase {
// 配置属性的基本类,抽象类
// 子类需要实现字符串和属性值之间的转换
};
template<typename F, typename T>
class LexicalCast {
// 用于转换的函数对象
// 只定义了调用操作符重载
};
template<typename T, typename FromStr = LexicalCast<std::string, T>,
typename ToStr = LexicalCast<T, std::string> >
class ConfigVar : public ConfigVarBase {
// 具体的配置属性
// 调用可函数对象 FromStr/ToStr 实现类型T和string之间的转换
// 不同类型的转换需要偏特化LexicalCast
};
class Config {
// 配置管理类
};
Config 通过静态方法返回静态成员,能够确保在其他成员初始化之前存在
配置系统的原则,约定优于配置:
template<T, FromStr, ToStr>
class ConfigVar;
template<F, T>
LexicalCast;
// 容器偏特化, 目前支持 vector
// list, set, map, unordered_set, unordered_map
// map/unordered_set 支持 key = std::string
// Config::Lookup(key), key相同
// 类型不同的,不会有报错,这个需要处理一下
自定义类型需要实现 sylar::LexicalCast偏特化 实现定义后,就可以支持Config解析自定义类型,自定义类型可以和常规stl容器一起使用
配置的事件机制 当一个配置项发生修改的时候,可以反向通知对应的代码,回调 该功实现使用了观察者模式?
logs:
- name: root
level: (debug,info,warn,fatal)
formatter: '%d%T%p%T%t%m%n'
appender:
- type: (StdoutLogAppender, FileLogAppender)
level: (debug,info,warn,fatal)
file: /log/xxx.log
sylar::Logger g_logger = sylar::LoggerMgr::GetInstance()->getLogger(name);
SYLAR_LOG_INFO(g_logger) << "xxxx log";
static Logger::ptr g_log = SYLAR_LOG_NAME("system");
// m_root, m_system-> m_root 当 logger 的appenders为空, 使用 root 写logger
在执行main之前添加一个日志配置更改回调函数, 从yaml 文件读取日志配置后,根据yaml的配置修改默认的日志配置
yaml日志配置
logs:
- name: root
level: INFO
formatter: "%d%T%m%n"
appenders:
- type: FileLogAppender
file: root.txt
- type: StdoutLogAppender
- name: system
level: DEBUG
formatter: "%d%T%m%n"
appenders:
- type: FileLogAppender
file: system.txt
formatter: "%d%T[%p]%T%m%n"
- type: StdoutLogAppender
对于配置文件中没有formatter属性的appender则使用log的formatter 例如, root和system的StdoutLogAppender均没有formatter,他们使用各自父节点的formatter
线程使用C++11的线程,互斥量使用的是pthread
Thread 类
线程同步的使用使用了区域锁(Scoped locking) 的方法, 它是RAII(Resource Acquisition Is Initialization)的一种具体应用 对象在构造函数初始化,在析构函数释放, 对于线程同步就是在对象构建就是上锁,对象析构就是解锁
以Mutex为例
template<typename T>
struct ScopedLockImpl {
public:
ScopedLockImpl(T& mutex)
: m_mutex(mutex) {
m_mutex.lock();
m_locked = true;
}
~ScopedLockImpl() {
unlock();
}
void lock() {
if (!m_locked) {
m_mutex.lock();
m_locked = true;
}
}
void unlock() {
if (m_locked) {
m_mutex.unlock();
m_locked = false;
}
}
private:
T& m_mutex;
bool m_locked;
};
class Mutex {
public:
typedef ScopedLockImpl<Mutex> Lock;
Mutex() {
pthread_mutex_init(&m_mutex, nullptr);
}
~Mutex() {
pthread_mutex_destroy(&m_mutex);
}
void lock() {
pthread_mutex_lock(&m_mutex);
}
void unlock() {
pthread_mutex_unlock(&m_mutex);
}
private:
pthread_mutex_t m_mutex;
};
Mutex的使用
Mutex mutex; // 定义互斥锁
void test_mutex() {
Mutex::Lock lock(mutex); // 创建Lock对象,初始化mutex, 加锁
// 临界区
} // 离开作用域自动调用析构,在析构中解锁
int main(int argc, char** argv) {
test_mutex();
return 0;
}
RWMutex(读写锁),Spinlock(自旋锁),CASLock(乐观锁)应用了同样的方式
日志模块整合线程,写日志线程安全
写文件,周期性, reopen
-
不同锁的区别以及底层原理
-
死锁问题,怎么防止死锁?
void Logger::setFormatter(LoggerFormatter::ptr val) {
MutexType::Lock lock(m_mutex);
m_formatter = val;
for (auto& i : m_appenders) {
MutexType::Lock ll(i->m_mutex);
if(!i->m_hasFormatter) {
i->m_formatter = m_formatter;
}
}
}
void Logger::setFormatter(const std::string &val) {
MutexType::Lock lock(m_mutex);
sylar::LoggerFormatter::ptr new_val(new sylar::LoggerFormatter(val));
if (new_val->isError()) {
std::cout << "Logger setFormatter name=" << m_name
<< " value=" << val << " invalid formatter";
return;
}
setFormatter(new_val);
}
定义协程接口 ucontext_t, macro
Thread-> main_fiber <--------> sub_fiber
^
|
|
v
sub_fiber
每个线程有一个主协程,主协程可以创建协程,切换到其他协程 其他协程(子协程)切换过程:需要先切换到主协程,由主协程切换到需要唤起的协程 子协程不能直接切换到另外一个子协程中。
1 - N 1 - M
scheduler --> thread --> fiber
1. 线程池, 分配一组线程
2. 协程调度器, 将协程,指定到相应的线程上去执行
N : M
m_threads
<function<void()>,fiber, threadid> m_fibers
schedule(func/fiber)
start()
stop()
run()
1. 设置当前线程的scheduler
2. 设置当前协程的run,fiber
3. 协程调度循环 while(true)
1. 协程消息队列里面是否有任务
2. 无任务执行,执行idle
IOManager(epoll) --> Scheduler
|
|
V
idle(epoll_wait)
信号量
PutMessage(msg, ) + 信号量1, sigle()
message_queue
|
|----- Thread
|----- Thread
wait()-信号量1, RecvMeassage(msg, )
异步IO, 等待数据返回。
Timer -> addTimer() --> cancel()
获取当前的定时器触发离现在的时间差
返回当前需要触发的定时器
总结
[Fiber] [Timer]
^ N ^
| |
| 1 |
[Thread] [TimerManager]
^ M ^
| |
| 1 |
[Scheduler] <---- [IOManager(epoll)]
sleep, usleep
+-----------+
|UnixAddress|
+-----+-----+
| ++IPv4Address
+-----+----+ +----------+ |
| Address +---+ IPAddress+-+
+-----+----+ +----------+ |
| ++IPv6Address
|
+-----+----+
| Socket |
+----------+
connect accept read/write/close
这部份内容参考 TLV 编码和Varint编码
write(int, float, int64, ...) read(int, float, int64, ...)