如何确保链式日志记录语句是原子的?

时间:2011-02-10 09:09:10

标签: c++ multithreading method-chaining

我有一个operator<<重载的日志记录类。所以我可以这样做:

oLogger << "Log this" << " and this" << " and " << 10 << endl;
oLogger`<< "Something else" << endl;

记录器没有任何问题。但是,我希望在线程之间共享logger对象。然后,我不希望它打印出这样的东西:

//LogFILE
Log this and this Something else
 and 10

所以,我需要锁定整个operator<<链。我猜这可以用RAII来完成,我还没有多想过。与此同时,有没有传统的方法来完成这项工作? (除了用操纵器结束输入?)

5 个答案:

答案 0 :(得分:4)

Nim回答的轻微替代方案:

创建

class LockedLog {
    static MutEx mutex; // global mutex for logging
    ScopedLock lock; // some scoped locker to hold the mutex
    Logger &oLogger; // reference to the log writer itself
public:
    LockedLog(Logger &oLogger) : oLogger(oLogger), lock(mutex) {}
    template <typename T>
    LockedLog &operator<<(const T &value) { oLogger << value; return *this; }
};

要么就这么做:

LockedLog(oLogger) << "Log this" << " and this " << " and " << 10 << endl;

或者将Logger::operator<<更改为常规方法,在LockedLog::operator<<中调用此方法,将强制转换操作符添加到Logger

operator LockedLog() { return LockedLog(*this); }

并且应该为当前代码添加锁定。

更新:锁定对operator<<的所有调用甚至可以锁定其参数的评估(取决于编译器是评估左侧还是右侧)争论首先,它可能会选择)。为了减少这种情况,可以:

class LockedLog {
    static MutEx mutex; // global mutex for logging
    std::stringstream buffer; // temporary formatting buffer;
    Logger &oLogger; // reference to the log writer itself
public:
    LockedLog(Logger &oLogger) : oLogger(oLogger), lock(mutex) {}
    template <typename T> 
    LockedLog &operator<<(const T &value) { buffer << value; return *this; }
    ~LockedLog() { ScopedLock lock(mutex); oLogger << buffer.str() << std::flush; }
};

但是stringstream增加了另一个开销。

答案 1 :(得分:3)

一种方法是使用宏,即

#define LOG(x) \
{\
  <acquire scoped lock> \
  oLogger << x; \
}

然后

LOG("Log this" << " and this" << " and " << 10 << endl);

我也是使用你上面提到的操纵器方法完成的,但是问题是你需要为所有类型实现operator<<(即不能使用存在的标准运算符)< / p>

编辑:为了减少锁定的时间,请考虑以下事项:

#define LOG(x) \
{\
  std::ostringstream str; \
  str << x; \       // the streaming happens in local scope, no need for lock
  oLogger.write(str.str()); \ // ensure the write method acquires a lock
}

答案 2 :(得分:2)

我发现最好的解决方案是编写一个类buffer以便

buffer(oLogger) << "Log this" << " and this" << " and " << 10 << endl;

创建一个临时缓冲区对象,捕获并格式化输出,并在其析构函数中将其写入oLogger。这可以通过包装stringstream来完成。因为每个线程都有自己的缓冲区,格式化是独立的。

为了获得额外的好感,buffer::~buffer可以使用几种不同的机制来防止oLogger的线程不安全访问。您假设来自多个线程的operator<<个调用可能是交错的。事实上,情况更糟;他们可以并发。你可以得到“LSoogm ethhiinsg else”。确保一次只有bufferoLogger次冲到{{1}}就可以防止这种情况发生。

答案 3 :(得分:2)

我可能会在这里使用表达式模板。

主要的想法是在格式化阶段获取锁定是非常愚蠢的,特别是因为在格式化过程中可能会有函数调用。

您需要使用两个不同的阶段:

  • 格式化日志
  • 原子地发布日志

这可以通过表达式模板完成:

  1. 首次致电Logger::operator<<会产生LoggerBuffer,其中嵌入了对Logger的引用。
  2. 后续调用在LoggerBuffer执行,处理所有格式混乱
  3. 销毁LoggerBuffer后(在语句末尾),它会锁定Logger,传递格式化字符串并解锁(除非你有一个无锁队列或其他东西)< / LI>

答案 4 :(得分:1)

由于我必须将日志国际化,我更喜欢以下内容:

oLogger << myAutosprintf(_("My wonderful %s ! I have %d apples"), name, nbApple);

这对翻译来说更好:)它会解决你的问题。 _()是翻译内容的快捷方式。

您可以使用gnu::autosprintfboost.format(感谢Jan Huec),也可以自己编写。

MY2C

注意:在发表好评后编辑(太快了,谢谢你的评论)。我删除了错误的“第一部分”声明