[英]Writing a simple C++ thread safe logging lib, how to make thread safe << operator?
我正在写一个简单的线程安全日志记录库的草图,我想到了一些事情。 这是代码:
#ifndef SimpleLogger_H
#define SimpleLogger_H
#include <iostream>
#include <mutex>
class SimpleLogger
{
public:
template <typename T>
static void log(T message)
{
mutex.lock();
std::cout << message;
mutex.unlock();
}
private:
static std::mutex mutex;
}LOG;
template <typename T>
SimpleLogger &operator<<(SimpleLogger &simpleLogger, T message)
{
simpleLogger.log(message);
return simpleLogger;
}
#endif //SimpleLogger_H
我的想法是像这样使用它:
LOG << "hello" << " world " << 8 << " I can mix integers and strings";
我了解上面的代码如下:
auto a1 = LOG.operator<<("hello");
auto a2 = a1.operator<<(" world ");
//Another thread may want to use LOG here, and would print in the middle of my message
auto a3 = a2.operator<<(8);
auto a4 = a3.operator<<(" I can mix integers and strings");
如您所见,由于<<
被分成多个函数调用,因此存在线程可以在我的消息中间使用LOG
对象的风险(我认为一条消息是<<
整个级联的消息)
另外,有没有一种方法可以自动为最后的<<
调用添加std::endl
? 我想不出一种方法,但是我看到某些日志记录库具有此功能
我该如何解决这两个问题?
我知道最好使用日志记录库,但是我想将android,desktop和ios日志记录混合到一个简单的lib中,而不需要高性能,而且我也为如何克服编写时遇到的困难感到困惑我自己的
您可以创建一个帮助程序类,该类收集所有输出并在销毁时打印。 大纲:
#include <string>
#include <iostream>
struct Msg;
struct Log {
void print(const Msg &m);
};
struct Msg {
std::string m;
Log &l;
Msg(Log &l) : l(l) {}
~Msg() {
// Print the message on destruction
l.print(*this);
}
};
void Log::print(const Msg &m) {
// Logger specific printing... here, append newline
std::cout << m.m << std::endl;
}
Msg &&operator << (Msg &&m, const std::string &s) {
// Append operator
m.m += s;
return std::move(m);
}
// Helper to log on a specific logger. Just creates the initial message
Msg log(Log &l) { return Msg(l); }
int main()
{
Log l;
log(l) << "a" << "b" << "c";
return 0;
}
由于Msg是本地消息,因此其他线程不会对其进行干扰。 可以在Log.print
方法中完成任何必要的锁定,该方法将接收完整的消息
正如其他人已经提到的那样,在发送到日志文件之前,需要一个本地缓冲区来收集消息。 在下面的示例中,SimpleLoggerBuffer对象被设计为仅用作临时变量。 即,它在表达式末尾被销毁。 析构函数将缓冲区刷新到日志中,因此您不必显式调用刷新函数(如果需要,也可以在其中添加endl)
#include <iostream>
#include <sstream>
#include <mutex>
using namespace std;
class SimpleLogger
{
public:
template <typename T>
static void log(T& message)
{
mutex.lock();
std::cout << message.str();
message.flush();
mutex.unlock();
}
private:
static std::mutex mutex;
}LOG;
std::mutex SimpleLogger::mutex;
struct SimpleLoggerBuffer{
stringstream ss;
SimpleLoggerBuffer() = default;
SimpleLoggerBuffer(const SimpleLoggerBuffer&) = delete;
SimpleLoggerBuffer& operator=(const SimpleLoggerBuffer&) = delete;
SimpleLoggerBuffer& operator=(SimpleLoggerBuffer&&) = delete;
SimpleLoggerBuffer(SimpleLoggerBuffer&& buf): ss(move(buf.ss)) {
}
template <typename T>
SimpleLoggerBuffer& operator<<(T&& message)
{
ss << std::forward<T>(message);
return *this;
}
~SimpleLoggerBuffer() {
LOG.log(ss);
}
};
template <typename T>
SimpleLoggerBuffer operator<<(SimpleLogger &simpleLogger, T&& message)
{
SimpleLoggerBuffer buf;
buf.ss << std::forward<T>(message);
return buf;
}
int main() {
LOG << "hello" << " world " << 8 << " I can mix integers and strings";
}
一个简单的解决方案是写入文件而不是标准输出,特别是将每个线程的文件分开。 这样就不需要锁定或任何其他同步。 如果行具有可解析的格式,则可以稍后合并文件。
另一种解决方案是从单个线程异步编写日志,并将消息最初存储在线程安全(可能无锁)队列中。
另外,有没有一种方法可以自动为最后的<<调用添加std :: endl?
除非我误会,否则您可以简单地进行stream << message << std::endl
。
我认为您可以简单地使用std :: clog 。 它是线程安全的,并且与std :: cout相反,旨在立即输出以进行日志记录。 从参考页面:
除非发出了sync_with_stdio(false),否则从格式化和未格式化输出的多个线程中并发访问这些对象是安全的。
最简单的方法是从第一个<<
返回一个临时代理-这可以在持续时间内记录您的流(并在销毁时解锁),也可以简单地构建本地ostringstream并在一个调用中将其刷新(再次销毁) 。
在日志记录时按住锁并不能提高性能(尽管它比您现有的log方法要好,后者应该使用std::lock_guard
来确保异常安全)。
构建和丢弃临时的ostringstream可能更好,但是如果您关心性能,则需要进行基准测试,并且可能最终需要做一些更复杂的操作(每个线程的循环缓冲区,mapped文件等)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.