每个线程一个类实例,C++11
One instance of class per thread, C++11
我正在为一个多线程应用程序编写一个日志实用程序,我希望能够以类似std::cout的方式调用它:
线程1:
Logger::log << "First message" << Logger::end;
线程2:
Logger::log << "Second message" << Logger::end;
一旦Logger::end
被传递到日志,消息应该被刷新到文件/屏幕/网络/日志的任何位置。为了处理对日志的并发写入而不混淆消息,我的想法是每个线程有一个Logger::log
实例,然后这些实例与一个工作线程共享对线程安全队列的访问权限,该工作线程专门用于弹出新消息并将其写入文件/屏幕等。
我想实现这一点的一种方法是让某种多单例返回一个实例,这取决于调用它的线程id(可能是从线程id映射到存储在std::map中的日志)。有没有更好和/或更有效的方法?
有没有其他设计不需要每个线程一个日志实例,而我忽略了这一点?std::cout如何处理并发访问?
谢谢!
您可以使用thread_local singletons:
class Logger {
public:
struct Sentinel{};
static thread_local Logger log;
static Sentinel end;
template<class T>
Logger& operator<<(T data) {
stream << data;
return *this;
}
//for endl and so on
Logger& operator<<(std::ostream& (*pf)(std::ostream&)) {
pf(stream);
return *this;
}
private:
Logger(){};
std::stringstream stream;
};
thread_local Logger Logger::log;
Logger::Sentinel Logger::end;
template<>
Logger& Logger::operator<<<Logger::Sentinel>(Logger::Sentinel data) {
stream << std::endl;
std::cout << stream.str();
stream.str("");
return *this;
}
另一种可能的语法:
class Logger_t {
public:
template<class T>
Logger_t& operator<<(T data) {
stream << data;
return *this;
}
//for endl and other stream manipulators
Logger_t& operator<<(std::ostream& (*pf)(std::ostream&)) {
pf(stream);
return *this;
}
void flush() {
std::cout << stream.str();
stream.str("");
}
private:
Logger_t(){};
std::stringstream stream;
friend Logger_t& Logger();
};
Logger_t& Logger() {
thread_local Logger_t logger;
return logger;
}
用法:
int main() {
Logger() << "test1 " << "test2" << std::endl;
Logger() << "test3" << std::endl;
Logger().flush();
Logger() << "test4" << std::endl; // <-- Not flushed
}
输出:
test1 test2
test3
编辑:
我重新审视了我的答案,虽然它展示了一般的想法,但具体的例子有一些注意事项:
- 虽然
std::cout
在默认情况下是线程安全的,但对operator<<
的多个并行调用中的各个字符仍然允许交错。据我所知,这至少不会发生在Ubuntu上的gcc和clang中,但要真正实现可移植性,您可能必须保护对std::cout
或日志系统使用的任何访问 - 您必须确保没有人将对Logger实例的引用传递给另一个线程。我不知道为什么要这样做,但这对其他用户来说可能是一个令人惊讶的限制,因为"正常"单身人士不是这样。因此,最好将缓冲区变量
stream
设为thread_local
,而不是记录器
我会跳过多单例。
有一个全局log
。CCD_ 9生成一个中间日志对象。intermediate log object << whatever
将中间日志对象的内部状态与其返回值共享。
当共享的内部状态最终被销毁时,它会自动将其写入注销。
因此(源代码的)每一行都是原子地发送出去的。
如果要进行多行日志记录,则必须使用auto&& l = log << whatever
,然后在需要追加时使用l << more stuff
。当l
对象被销毁时,它被发送到日志输出。
工业质量:
在log
和intermediate_log
上,<< X
应该与ostream << X
一样工作。
intermediate_log
应该存储一个带有magic deleter的std::shared_ptr<std::stringstream>
,并将其输入转发到该deleter,然后返回其自身的副本(该副本允许使用auto
语法延长生存期)。
magic deleter应该将stringstream
内容写入实际输出日志,可能通过异步队列或其他方式(如果存在大量争用,并且写入速度较慢)。
根据直接的问题,最好合理的方法是将记录器(如thread_local
变量)存储在singleton中。
但总的来说,我强烈反对你的想法。您应该直接从消息源线程将日志刷新到文件。否则,当程序在消息发送到队列和实际写入光盘之间崩溃时,您可能会丢失一些消息。
我可能在这里建议简单的解决方案,但我使用具有线程安全性的常规单例类:
class Logger{
private:
std::mutex mtx;
...
public:
void log (const std::string& logToPrint){
std::lock_guard<std::mutex>(mtx);
//do any logging here..
}
};
- 在Visual Studio中单实例Qt应用程序版本5.11.1中,在所有其他窗口的顶部打开Qt MainWindow
- 防止复制构造函数实例化 C++11 类"deleting"
- 静态库中只有一个C 11 Singleton的实例
- C++11 列出 push_back() 实例化错误
- 只有 std::mt19937 的实例在 c++11 中重复值
- 如何使用 C++11 unique_ptr实例化我的代码
- 错误:"Class"在其自己的定义中隐式实例化。C++11
- C 11枚举类实例化
- 如何在 C++11 中创建结构的编译时常量实例
- 每个线程一个类实例,C++11
- 如何使用带有实例方法的C++11线程
- C++11风格的SFINAE和模板实例化上的函数可见性
- 释放 C++11 中动态分配的uv_timer_t (libuv) 实例
- 无法使用 c++11 实例化抽象类
- C++11 :从当前实例(此)进行unique_ptr的解决方法
- 如果使单一实例构造函数受到保护,缺点是什么 - 继承 - C++11.
- C++11 相当于 Java 的实例是什么
- 模板实例化 - 使用 C++11 的大小时没有匹配函数...() 具有某些类的运算符
- 是否可以在C++11中创建每个实例的mixin
- 构造函数中的隐式实例C++11