每个线程的一个类实例,C ++ 11

时间:2015-03-27 21:06:29

标签: c++ multithreading c++11 stdout

我正在为多线程应用程序编写一个日志记录实用程序,我希望能够以类似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如何处理并发访问?

谢谢!

4 个答案:

答案 0 :(得分:3)

你可以使用thread_local单例:

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

编辑:
我重新回答了我的答案,虽然它展示了一般的想法,但具体的例子有一些注意事项:

  1. 虽然std::cout是 - 默认情况下 - 线程安全,但仍允许对来自operator<<的多个并行调用的单个字符进行交错。据我所知,这至少不会发生在Ubuntu上的gcc和clang,但要真正可移植,你可能需要保护对std::cout或任何日志系统使用的任何访问。
  2. 您必须确保没有人将对Logger实例的引用传递给另一个线程。我不知道为什么要这样做,但对于其他用户来说这可能是一个令人惊讶的限制,因为对于&#34;正常&#34;单身。因此,最好使缓冲区变量stream thread_local而不是记录器。

答案 1 :(得分:2)

我跳过那个多单身。

拥有全球loglog << whatever生成一个中间日志对象。 intermediate log object << whatever与其返回值共享中间日志对象的内部状态。

当共享内部状态最终被销毁时,它会以原子方式将其写入注销。

所以每一行(来源)都是原子地发出的。

如果要进行多行日志记录,则必须使用auto&& l = log << whatever,然后在要添加时使用l << more stuff。当l对象被销毁时,它将被发送到日志输出。

对于工业质量:

logintermediate_log,如果<< X有效,则ostream << X应该有效。

intermediate_log应该存储带有魔术删除器的std::shared_ptr<std::stringstream>,然后将其输入转发给,然后返回自身的副本(该副本允许使用auto语法进行有效期延长。)< / p>

魔术删除器应该将stringstream内容写入实际的输出日志,可能是通过异步队列或其他东西(如果存在大量争用,写入速度很慢)。

答案 2 :(得分:0)

根据直接问题,最合理的方法是在单例内存储类似thread_local变量的记录器。

但我强烈反对你的想法。您应该直接从消息源线程将日志刷新到文件。否则,当您的程序在消息发送到队列并实际写入光盘之间崩溃时,您可能会丢失一些消息。

答案 3 :(得分:0)

我可能会在这里建议一些简单的解决方案,但我会选择具有线程安全性的常规单例类:

class Logger{
   private:
     std::mutex mtx;
     ...
   public:
   void log (const std::string& logToPrint){
      std::lock_guard<std::mutex>(mtx);
      //do any logging here..
   } 
};