2

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

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

记录器可以毫无问题地做到这一点。但是,我希望在线程之间共享记录器对象。然后,我不希望它打印出这样的内容:

//LogFILE
Log this and this Something else
 and 10

所以,我需要锁定整个operator<<s 链。我猜这可以用 RAII 来完成,我还没有考虑太多。与此同时,是否有任何传统的方式来完成这项工作?(除了用机械手结束输入?)

4

5 回答 5

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<<,将 cast-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增加了另一个开销。

于 2011-02-10T09:57:36.887 回答
3

一种方法是使用宏,即

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

然后

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

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

编辑:为了减少持有锁的时间,考虑这样的事情:

#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
}
于 2011-02-10T09:13:09.377 回答
2

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

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

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

为了额外的花哨,buffer::~buffer可以使用几种不同的机制来防止oLogger. 您假设operator<<来自多个线程的调用可能是交错的。事实上,情况更糟;它们可以是并发的。你可以得到“LSoogm ethhiinsg else”。确保一次只有一个buffer冲洗可以oLogger防止这种情况发生。

于 2011-02-10T10:36:13.697 回答
2

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

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

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

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

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

  1. 第一次调用Logger::operator<<产生一个LoggerBuffer嵌入对 的引用Logger
  2. 执行后续调用LoggerBuffer,处理所有格式混乱
  3. 在销毁LoggerBuffer(在语句末尾)时,它会锁定Logger,传递格式化的字符串,然后解锁(除非你有一个无锁队列或其他东西)
于 2011-02-10T10:44:11.377 回答
1

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

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

翻译更好:) 它会解决你的问题。_()是翻译东西的捷径。

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

my2c

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

于 2011-02-10T09:15:52.550 回答