Как убедиться, что цепочечный оператор записи является атомарным? - PullRequest
2 голосов
/ 10 февраля 2011

У меня есть класс журналирования, который перегружен operator<<. Так что я могу делать такие вещи:

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

Регистратор делает это без проблем. Но я хочу, чтобы объект регистратора был общим для потоков. Тогда я не хочу, чтобы это распечатывало что-то вроде этого:

//LogFILE
Log this and this Something else
 and 10

Итак, мне нужно заблокировать целую цепочку operator<< с. Я предполагаю, что это может быть сделано с RAII, я еще не думал об этом. В то же время, есть ли традиционный способ сделать это? (Кроме окончания ввода с помощью манипулятора?)

Ответы [ 5 ]

4 голосов
/ 10 февраля 2011

Небольшая альтернатива ответу Нима:

Создать

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 добавляет еще одну накладную.

3 голосов
/ 10 февраля 2011

Одним из подходов является использование макроса, т.е.

#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
}
2 голосов
/ 10 февраля 2011

Я бы, вероятно, использовал здесь шаблоны выражений.

Основная идея заключается в том, что чертовски глупо получать блокировку на этапе форматирования, тем более что во время этого форматирования могут быть вызовы функций.

Вам необходимо использовать две разные фазы:

  • форматирование журнала
  • атомарное размещение журнала

Это может быть выполнено с помощью шаблонов выражений:

  1. При первом вызове Logger::operator<< получается LoggerBuffer, который включает ссылку на Logger.
  2. Последующие вызовы выполняются на LoggerBuffer, который имеет дело со всей ошибкой форматирования
  3. После уничтожения LoggerBuffer (в конце оператора) он блокирует Logger, передает отформатированную строку и разблокирует (если у вас нет очереди без блокировки или чего-то еще)
2 голосов
/ 10 февраля 2011

Я обнаружил, что лучшее решение - написать класс buffer, чтобы

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

создает временный буферный объект, захватывает и форматирует вывод и записывает его в oLogger в его деструкторе. Это тривиально сделать, обернув stringstream. Поскольку каждый поток имеет свои собственные буферы, форматирование является независимым.

Для дополнительной фантазии buffer::~buffer может использовать несколько различных механизмов для предотвращения небезопасного доступа к oLogger. Вы предполагали, что operator<< вызовы из нескольких потоков могут чередоваться. На самом деле, это хуже; они могут быть параллельными. Вы можете получить "LSoogm ethhiinsg else". Удостоверьтесь, что только один buffer сбрасывает к oLogger одновременно, предотвращает это.

1 голос
/ 10 февраля 2011

Поскольку я должен интернационализировать логи, я предпочитаю такие вещи как:

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

Это намного лучше для перевода :) И это решит вашу проблему._() - это ярлык для перевода.

Вы можете использовать gnu :: autosprintf , boost.format (спасибо Jan Huec) или написать свой собственный.

my2c

Примечание: отредактировано после хороших замечаний (было слишком быстро, спасибо за комментарии).Я стер неправильный оператор "первая часть"

...