如何使用线程局部存储实现多线程下的日志系统
副标题[/!--empirenews.page--]
概述通常来说,在应用程序中需要日志来记录程序运行的状态,以便后期问题的跟踪定位。在日志系统的设计中,通常会有一个总的日志系统来统一协调这些日志的设置如位置、输出级别和内容等。在多线程编程中,当每个线程都需要输出日志时,因为要考虑线程间的同步,日志系统的设计更加复杂。 在单线程应用程序中,通常使用一个日志单例向某个文件输出应用运行过程中的重要日志信息,但是在多线程环境中,这样做显然不好,因为各个线程打印出的日志会错综复杂从而使得日志文件不容易阅读和跟踪。比较好的办法是主线程记录自己的日志,各个子线程单独记录各自的日志。为了保留多线程环境中日志系统的简单清晰特性,本文使用线程局部变量来实现多线程下的日志系统。既保证了各个线程有各自清晰的日志文件,每个线程又只有一个简单的日志单例,从而使日志系统具有简单清晰高效的特性并且适用单线程和多线程的应用。 本文所阐述的多线程日志系统是基于 C++和 Boost 库的一个实现,对于实现多线程环境下的日志系统有很好的参考意义。如果日志系统的最初设计没有考虑多线程环境,随着业务的发展需要实现多线程,这种方法可以很方便地改造已有的日志系统,从而实现多线程日志系统,并且这种方法还能保持原来日志系统的业务接口不变。 背景介绍 对于线程局部存储的概念,正如字面意思,每个变量在每个线程中都有一份独立的拷贝。通过使用线程局部存储技术,可以避免线程间的同步问题,并且不同的线程可以使用不同的日志设置。通过 Boost 库的智能指针 boost::thread_specific_ptr 来存取线程局部存储,每个线程在第一次试图获取这个智能指针的实例时需要对它进行初始化,并且线程局部存储的数据在线程退出时由 Boost 库来释放。 使用线程局部变量的多线程日志的优势: 使用 static 的线程局部变量很容易能实现线程级别的单例日志系统; 通过智能指针 boost::thread_specific_ptr 来存取线程局部存储的生成和清除工作简单方便; 使用线程局部变量很容易实现对一个已有的单例日志系统进行多线程支持的改造,并且不用改动任何原来的日志接口; 单线程环境的日志 一般来说,日志类都会实现成一个单例,从而方便调用。为简单起见,示例中的日志代码的写操作直接打印到控制台。以下是 Logger 类的初始定义: 清单 1. Logger 类的初始定义 class Logger { private: Logger() { } public: static void Init(const std::string &name); static Logger *GetInstance(); void Write(const char *format, ...); private: static std::string ms_name; static Logger *ms_this_logger; }; Init()函数用来设置 Logger 的名字。在实际应用中,可以用来设置其它 Logger 的配置信息。在单线程环境中,每次调用 Write()函数就可以写日志了。 多线程环境的日志 多线程环境下实现日志系统,必须对写操作加锁,否则将得到混乱的输出。容易想到的方法是:在 Logger 类中维护一个列表,按名字存放所有线程中 Logger 的实例,并在每个线程中按名字查找并使用线程自己的唯一一个 Logger。 这跟 log4j 的实现有点像,不同的是 log4j 把所有的 Logger 配置都放在配置文件里,每个 Logger 都有独立的配置。但是我们的 Logger 无法实现这个功能,因为配置信息都是运行时传入的,并且所有的 Logger 共享的同样的配置信息。 另外一个很大的问题是,必须修改 GetInstance()的声明,加入一个类似 Logger 名字的参数。这种做法破坏了原有的 API,已有的代码必须全部修改以支持新的 Logger 类。 使用线程局部存储,可以解决以上两个问题:每个 Logger 的配置都是线程独立,并且不需要修改 GetInstance()的声明。以下是新的 Logger 类的声明,里面使用了 boost 的 thread_specific_ptr 这个类,它实现了跨平台的线程局部存储解决方案。 清单 2. 使用线程局部存储的 Logger 类定义 class Logger { private: Logger() { } public: static void Init(const std::string &name); static Logger *GetInstance(); void Write(const char *format, ...); private: static boost::thread_specific_ptr<std::string> ms_name; static boost::thread_specific_ptr<Logger> ms_this_logger; }; 代码中简单地使用 boost::thread_specific_ptr 类重新声明了类里的两个静态变量,在运行时它们会被放到线程局部存储中。 清单 3. 使用线程局部变量的 Logger 类实现 void Logger::Init(const string &name) { if (!name.empty()) { ms_name.reset(new std::string(name)); } } Logger *Logger::GetInstance() { if (ms_this_logger.get() == NULL) { ms_this_logger.reset(new Logger); } return ms_this_logger.get(); } (编辑:我爱故事小小网_铜陵站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |