多线程日志帮助

Posted

技术标签:

【中文标题】多线程日志帮助【英文标题】:Multithreaded Logging Help 【发布时间】:2011-07-19 09:31:07 【问题描述】:

我正在为我的引擎开发多线程日志记录。但是我在制作 MT 时遇到了麻烦。问题是,即使我将记录器对象深度复制到局部变量中,我也会遇到文件问题。因为两个线程将同时写入同一个文件,这会造成混乱。这是我的记录器类:

class Logger 
public:
    typedef std::vector<LogListener *> ListenerList;

private:
    ListenerList listeners;
            boost::mutex mutex;

public:
    Logger();
    ~Logger();

    Logger * write(const String &line);

;

Logger * Logger::write(const String &text) 

    if(listeners.empty()) return this;
    boost::unique_lock<boost::mutex> lock(mutex);
    for(ListenerList::iterator i = listeners.begin(); i != listeners.end(); ++i) 
        (*i)->write(text);
    

    return this;



class FileLogListener : public LogListener 
    std::ofstream stream;
public:
    FileLogListener(const String &str) : stream(str.c_str(), std::ios::out | std::ios::app)  
    void write(const String &text) 
        stream << text << std::endl;
    

;

现在假设(避免互斥体):

//Thread 1

void func1() 
    Logger * log = new Logger;
    log->addListener("file.txt");
    log->write("Thread 1 Test");


//Thread 2

void func2() 
    Logger * log = new Logger;
    log->addListener("file.txt");
    log->write("Thread 2 test");


int main() 
  boost::thread t1(&func1);
  boost::thread t2(&func2);

  t1.join();
  t2.join();
  return 0;

“file.txt”变得一团糟。

编辑:目前,我正在阅读和观看有关多线程的讲座以更好地理解它。

编辑:上面的记录器有效。

提前致谢, 加西姆加西姆扎达

【问题讨论】:

@littleadv: Ubuntu Linux 您的线程不起作用,因为您在 func1func2 中使用了不同的互斥锁。您希望您的互斥锁在处理写入的类中,然后在写入周围有一个作用域锁(以便文件的每个锁都引用同一个互斥锁)。或者在func1func2 中有一个指向同一个互斥锁的指针 我忘记编辑了。我现在修好了。 【参考方案1】:

要么不写入同一个文件,要么将写入序列化(例如通过使用锁)。

【讨论】:

你的意思是文件锁?还是线程锁?如果您的意思是文件锁,您知道我可以在其中实现它的库吗?谢谢 @Gasim:Boost 进程间库执行文件锁定:boost.org/doc/libs/1_47_0/doc/html/interprocess/… 谢谢汤姆。我现在就试试。 这个库不保证文件锁在多线程的一个进程中不会崩溃。 ://【参考方案2】:

一旦我写了这个article about minimalist logger。它是一个可以在 MT 环境中使用的单头文件简单记录器。它满足了我在很多项目中的日志记录需求

如果你想使用你的记录器,你可以检查方法

简而言之:它只是通过 boost::mutex 锁定对文件的访问

【讨论】:

【参考方案3】:

你应该给 log4cxx 一个机会。

我只知道这个记录器框架的 java 和 c# 版本,但它们很棒。

而且您不必担心多线程,可以专注于您的实际工作。

您还可以轻松地将日志目标从文件更改为 sql 甚至 udp,只需更改配置文件即可。

http://logging.apache.org/log4cxx/index.html

【讨论】:

我已阅读link。并检查了那里发布的所有库(log4cxx 也在那里)。这些库对于日志系统来说是巨大的,它们有太多我不想要的特性。这就是为什么我试图用 MT 编写我自己的非常轻量级的记录器类。【参考方案4】:

为每个文件创建一个单独的Logger 对象,并在其写入方法中保存一个互斥锁。

【讨论】:

【参考方案5】:

在 Ubuntu Linux 中,可能有一个 syslog 守护程序正在运行。利用它,不要重新发明***。

【讨论】:

利用它是什么意思?【参考方案6】:

我认为你遇到的困难是你有多个文件监听器都指向同一个文件。这意味着您没有一个互斥锁来捕获对文件的所有尝试写入。解决此问题的一种方法是向您的 lisener 添加一个静态互斥锁:

class FileLogListener : public LogListener 
    std::ofstream stream;
    static  boost::mutex m_mutex; //same mutex for all writes to (any) file
public:
    FileLogListener(const String &str) : stream(str.c_str(), std::ios::out | std::ios::app)  
    void write(const String &text) 
        boost::mutex::scoped_lock lock(m_mutex);//lock the mutex
        stream << text << std::endl;
    

;

 //somewhere in  you cpp file, initialize the mutex
 boost::mutex FileLogListener::m_mutex;

我还认为您设置的记录器过于复杂(也许您有超出此问题范围的原因)。将来您可能会遇到的一个问题是,当您想要优化代码时,您可能会发现很难关闭注销。

以防万一你觉得它有用,我想我会发布一个替代方案,

#include <iostream>
#include <fstream>
#include <boost/thread.hpp>
template<class T>
struct threadsafe

  static boost::mutex m_mutex;
  static void print(const std::string& msg) 
  
    boost::mutex::scoped_lock lock(m_mutex);
    T::print(msg);
  
;


struct file_print

  static std::ofstream stream;
  static void print(const std::string& msg) 
  
    stream<<msg;
  
;
struct console_print

  static void print(const std::string& msg) 
  
    std::cout<<msg<<std::endl;
  
;


struct dont_log

  static void print(const std::string& msg) 
  
;

//somewhere in the library c++ file
template<class T>
boost::mutex threadsafe<T>::m_mutex;

然后您可以像这样使用记录器:

template<class logger = dont_log>
struct my_class


  void run()
  
    logger::print("msg\n");
  

;

std::ofstream file_print::stream("test.txt");

int
main  (int ac, char **av)


  my_class< threadsafe<file_print> > c;
  //or log to the console
  //my_class< threadsafe<console_print> > c;
  //or don't log at all (by default) 
  //my_class<> c;
  c.run();

此记录器的优势在于,如果您(或您的用户)决定不再记录,他们只需将模板参数切换为dont_log。由于 print 函数是静态的,编译器应该删除对空函数的所有引用,并优化所有日志记录。

【讨论】:

这是一个好主意,但是通过我的方法,我可以在我的记录器类中有一个空的侦听器列表,并且不会记录任何内容。这个记录器还没有完成,因为我需要添加过滤器。但现在不需要,因为我正在将所有内容都写入一个文件和控制台

以上是关于多线程日志帮助的主要内容,如果未能解决你的问题,请参考以下文章

C#使用线程窗口调试多线程程序

C# log4net 多线程问题

logging:多线程调试时用来代替print和单步调试

C#使用线程窗口调试多线程程序

日志收集系统-多线程消息队列

代码就支持了多线程并发