c++ - 使以下记录器实现线程安全的更好方法?

标签 c++ multithreading c++11 logging

我想编写一个基本的线程安全记录器,具有类似 cout 的接口(interface)。我想出了以下类(class)设计。这绝对不是最好的设计,因为如果使用错误,它可能会陷入死锁,如 int main() 所示。

#include <iostream>
#include <sstream>  // for string streams 
#include <mutex>
#include <memory>

typedef std::ostream&(*endl)(std::ostream&);

class BasicLogger {

public:
  enum SEVERITY {
    CRITICAL,
    ERROR,
    WARNING
  };

  explicit BasicLogger(SEVERITY _s): s(_s) {
    streamMutex.lock();
    logStream.reset(new std::ostringstream);
  }

  ~BasicLogger() {
    std::cout << logStream->str();
    streamMutex.unlock();
  }

  std::ostringstream& operator<< (const endl eof) {
    (*logStream) << eof;
    return (*logStream);
  }

  template<typename T>
  std::ostringstream& operator<< (const T& obj) {
    (*logStream) << obj;
    return (*logStream);
  }

  static std::unique_ptr<std::ostringstream> logStream;

  BasicLogger(const BasicLogger&) = delete;
  BasicLogger& operator=(const BasicLogger&) = delete;

private:
  SEVERITY s;          //TODO
  static std::mutex streamMutex;

};

/*=======================================================*/
std::unique_ptr<std::ostringstream> BasicLogger::logStream;
std::mutex BasicLogger::streamMutex;
/*=======================================================*/

int main() {

  int a = 9;
  int b = 8;

  // BasicLogger l(BasicLogger::ERROR); //Deadlock situation

  BasicLogger(BasicLogger::ERROR) << "Linux" << " " << a << " " << b << std::endl;
  BasicLogger(BasicLogger::ERROR) << "MyMachine";
  BasicLogger(BasicLogger::ERROR) << std::endl;

}

最佳答案

您在构造函数中锁定互斥锁,并在析构函数中解锁它。 因此,不可能同时创建多个 BasicLogger 实例。

BasicLogger l(BasicLogger::ERROR); 将调用构造函数,从而获取锁。在 l 超出范围之前,不会调用析构函数,这意味着互斥体保持锁定状态,直到 l 超出范围。

如果您尝试构造另一个 BasicLocker,您的构造函数会尝试获取在 l 被销毁之前不可用的锁,从而导致死锁。

当您创建临时 BasicLogger 实例时,BasicLogger(BasicLogger::ERROR) 会调用构造函数,使用该对象,然后立即销毁。因此,被锁定的互斥体被解锁。


由于您要为每个 BasicLogger 实例创建独立的 std::stringstream,因此您需要一个锁来保护对 std::stringstream 的访问,因此多个线程可以写入同一个记录器。因此,每个实例都应该持有一个互斥锁。

您还需要一个静态互斥体来保护对 std::cout 的同时访问。日志打印时获取锁,立即释放。当然,这要求所有对 std::cout 的访问都通过 BasicLogger 进行。

class BasicLogger {
public:
    BasicLogger() = default;
    ~BasicLogger() {
        std::lock_guard<std::mutex> lLock(localMutex); /* the order of locking is important */
        std::lock_guard<std::mutex> gLock(globalMutex);
        std::cout << stream.str();
    }

    /* TODO: satisfying the rule of 5 */

    template <class T>
    BasicLogger& operator<< (const T& item) {
        std::lock_guard<std::mutex> lLock(localMutex);
        stream << item;
        return *this;
    }

private:
    std::ostringstream stream;
    std::mutex localMutex;
    static std::mutex globalMutex;
};

关于c++ - 使以下记录器实现线程安全的更好方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55210628/

相关文章:

c++ - 你应该始终在构造函数中保留可选参数吗

c++ - 如何从双端队列中提取元素?

java - `Thread.checkAccess()`是 `Thread.suspend()`的适当替代品吗?

c++ - 我应该如何更正此 priority_queue 比较功能?

c++ - 如何扩展对可变参数模板基类的调用?

c++ - 您是否将模块特定的功能声明为静态的?

c++ - C4477 : 'fprintf' : format string '%s' requires an argument of type 'char *' , 但可变参数 1 的类型为 'int *'

android - ListView 的多线程使错误的项目/根本没有项目出现

python - 如何防止 PyQt 对象从不同线程收集垃圾?

c++ - 如何使用可变参数模板在 C++ 中获取参数的变量号及其大小