前文
日志库spdlog(一) 源码面前,了无秘密 正如前文所说,本文将聚焦于spdlog在并发方面的工作,同时提点一下值得一提的技术。
线程安全
C++支持多线程编程,既然支持并发(并发或者并行是有区别的,但常规而言并发用的更多,读者明白就好),那spdlog库就天然存在重入数据竞争的问题。 比如,在registry层,存储一个name to logger的映射,此时如果并发写入,显然会有UB。 再比如,如果多个线程同时对一个logger写入日志,那此时如果没有保护,显然输出会乱掉。
spdlog为了支持并发,几乎在每一层都放了mutex来保护数据。
registry层
registry层主要有三个锁,如下:
1std::mutex logger_map_mutex_, flusher_mutex_;
2std::recursive_mutex tp_mutex_;
前两个锁结合代码都很好理解:前者用于保护name to logger结构,后者用于保护定时会写的结构(支持定时刷盘,多线程实现)。 在对logger执行操作时,此时不希望logger被更改,应当上锁,以达到快照功能:
1SPDLOG_INLINE void registry::set_level(level::level_enum log_level) {
2 // 如果要设置日志等级,此时就不允许其他线程修改logger
3 std::lock_guard<std::mutex> lock(logger_map_mutex_);
4 for (auto &l : loggers_) {
5 l.second->set_level(log_level);
6 }
7 global_log_level_ = log_level;
8}
最后一个则是用到了std::recursive_mutex,这是C++的可重入锁,也就是说可以被多个owner持有,内部存在计数,被锁上多少次就要被解锁多少次。
其作用暂且按下不表,等将异步时再说明。
logger层
logger负责整合sink,实际上的并发安全是由sink保证的。当然这里logger初始化的时候会有些风险,显然你不应该在两个线程内同时去初始化同一个logger
sink层
该层负责真正写入时的并发安全,但sink基类却没有锁,这就比较奇怪了,回过头我们再看看两种sink的实现:
1// ansicolor_sink.h
2template <typename ConsoleMutex>
3class ansicolor_sink : public sink {
4 ...
5 using mutex_t = typename ConsoleMutex::mutex_t;
6 mutex_t &mutex_;
7 ...
8}
9
10using ansicolor_stdout_sink_mt = ansicolor_stdout_sink<details::console_mutex>;
11using ansicolor_stdout_sink_st = ansicolor_stdout_sink<details::console_nullmutex>;
12
13// basic_file_sink.h
14template <typename Mutex>
15class basic_file_sink final : public base_sink<Mutex> {
16};
17
18using basic_file_sink_mt = basic_file_sink<std::mutex>;
19using basic_file_sink_st = basic_file_sink<details::null_mutex>;
20
21// base_sink.h
22template <typename Mutex>
23class SPDLOG_API base_sink : public sink {
24 ...
25 Mutex mutex_;
26 ...
27};
可以看到,对于不同的sink,都会定义一个mutex,以此实现多线程下的写入安全。 在使用部分,则是在log、flush等多处使用,保证临界区写入安全。 对于一个sink,提供了_mt _st两类后缀类型实例,传入了正常的mutex锁以及只实现了lock、unlock两个空方法的结构,有兴趣可以看看。 这样对于任意一个类型的sink,不必两种类型都实现一遍,空方法编译器是会优化掉的。
小结
spdlog在并发上做的很严谨,正如上文所说,锁对性能也有一定影响,锁基本也只加在了需要的地方。 显然,针对单线程场景,我们使用single thread类型的sink,针对多线程场景,则使用multi thread类型的,便捷切换,直接改个mt即可,你大概率也不会有任何心智负担。