繁体   English   中英

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

[英]Better way to make following logger implementation thread safe?

我想用cout这样的界面编写一个基本的线程安全记录器。 我想出了以下课程设计。 这绝对不是最好的设计,因为如果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::stringstreamBasicLogger需要一个锁来保护对std::stringstream访问,以便多个线程可以写入同一记录器。 因此,每个实例都应拥有一个互斥量。

您还需要一个静态互斥锁,以保护同时访问std::cout 当日志被打印并立即释放时,将获得锁定。 当然,这需要通过BasicLogger进行对std::cout所有访问。

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;
};

我将只考虑一个operator<< ,并且仅在锁定互斥量的成员函数中考虑。 因此,只有在要写时才握住锁。

而不是静态变量(基本上与全局变量相同,因此您不能有多个记录器)来保存std::ostringstream ,而成员变量包含std::ostream& 这意味着通过多个BasicLogger编写多个事物将使它们看起来混合在一起,但这已经是多个线程通过同一个BasicLogger编写的问题。

要解决如下问题:

BasicLogger l;

// Thread 1:
l << 1 << 2;

// Thread 2:
l << 3 << 4;

// Output is one of:
1234
1324
1342
3124
3142
3412
// Ideally it should only be
1234
3412
// (Pretend `1` is something like "x is: " and `3` is "y is: ")
// (You wouldn't want "x is: {y}" or "x is: y is: {x} {y}")

您可以使用一个函数,该函数先编写各种内容,然后使用可变参数将其锁定。 (在我的示例中写为BasicLogger::write

它看起来像这样:

#include <iostream>
#include <utility>
#include <mutex>
#include <thread>

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

    // Consider logging to std::cerr by default instead
    explicit BasicLogger(SEVERITY s = BasicLogger::ERROR, std::ostream& out = std::cout)
        : severity(s), output(&out) {}

    explicit BasicLogger(std::ostream& out = std::cout)
        : severity(BasicLogger::ERROR), output(&out) {}

    BasicLogger(const BasicLogger&) = default;

    template<typename T>
    BasicLogger& operator<<(T&& obj) {
        std::lock_guard<std::mutex> lock(stream_mutex);
        (*output) << std::forward<T>(obj);
        return *this;
    }

    template<typename... T>
    void write(T&&... obj) {
        std::lock_guard<std::mutex> lock(stream_mutex);
        ((*output) << ... << std::forward<T>(obj));
    }

    std::ostream& get_output() noexcept {
        return *output;
    }
    const std::ostream& get_output() const noexcept {
        return *output;
    }

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

    SEVERITY severity;
private:
    std::ostream* output;
    static std::mutex stream_mutex;
};

std::mutex BasicLogger::stream_mutex;


int main() {
    BasicLogger l(std::cerr);
    int x = 0, y = 1;

    std::thread t1([&]() {
        l.write("x is: ", x, '\n');
    });
    std::thread t2([&]() {
        l.write("y is: ", y, '\n');
    });
    t1.join();
    t2.join();
}

或者甚至可以有一个operator<<(std::tuple<T...>) ,而不是l.write(...) ,而是l << std::tie(...)

但是请注意本课程与您的课程之间的区别。 您的类只写一次,使用空间来拥有一个临时的ostringstream ,而这会多次直接写入所需的ostream

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM