Асинхронное поточно-безопасное ведение журнала в C ++ (без мьютекса) - PullRequest
7 голосов
/ 16 ноября 2011

Я на самом деле ищу способ сделать асинхронную и поточно-ориентированную запись в журнал в моем C ++.

Я уже исследовал решения для поточно-ориентированной регистрации, такие как log4cpp, log4cxx, Boost: log или rlog,но похоже, что все они используют мьютекс.И, насколько мне известно, мьютекс - это синхронное решение, что означает, что все потоки заблокированы, когда они пытаются написать свои сообщения, в то время как другие пытаются это сделать.

Знаете ли вы решение?

Ответы [ 6 ]

6 голосов
/ 16 ноября 2011

Я думаю, что ваше утверждение неверно: использование мьютекса не обязательно эквивалентно синхронному решению. Да, Mutex предназначен для управления синхронизацией, но его можно использовать для разных целей. Мы можем использовать мьютекс, например, в очереди потребителя-производителя, пока ведение журнала все еще происходит асинхронно.

Честно говоря, я не смотрел на реализацию этой библиотеки журналов, но было бы целесообразно создать асинхронного приложения (для log4j, такого как lib), который регистрирует записи в очередь потребителя источника, а другой рабочий поток отвечает за запись в файл (или даже делегировать другому заявителю), если он не предоставлен.


Edit: Просто у меня было короткое сканирование в log4cxx, он предоставляет AsyncAppender, который делает то, что я предложил: буферизирует входящее событие регистрации и асинхронно делегирует присоединенному приложению.

5 голосов
/ 16 ноября 2011

Если я правильно понял ваш вопрос, вы обеспокоены выполнением операции ввода-вывода (возможно, записи в файл) в критической секции логгера.

Boost: log позволяет вам определить собственный объект записи.Вы можете определить operator () для вызова асинхронного ввода-вывода или передачи сообщения в ваш журнал (который выполняет операции ввода-вывода).

http://www.torjo.com/log2/doc/html/workflow.html#workflow_2b

5 голосов
/ 16 ноября 2011

Я бы рекомендовал избежать этой проблемы, используя только один поток для ведения журнала.Для передачи необходимых данных в журнал можно использовать очередь fifo без блокировок (потокобезопасен, если производитель и потребитель строго разделены, и каждая роль имеет только один поток - поэтому вам потребуется одна очередь для каждого производителя.)

Пример быстрой очереди без блокировки:

queue.h:

#ifndef QUEUE_H
#define QUEUE_H

template<typename T> class Queue
{
public:
    virtual void Enqueue(const T &element) = 0;
    virtual T Dequeue() = 0;
    virtual bool Empty() = 0;
};

hybridqueue.h:

#ifndef HYBRIDQUEUE_H
#define HYBRIDQUEUE_H

#include "queue.h"


template <typename T, int size> class HybridQueue : public Queue<T>
{

public:
    virtual bool Empty();
    virtual T Dequeue();
    virtual void Enqueue(const T& element);
    HybridQueue();
    virtual ~HybridQueue();

private:
    struct ItemList
    {
        int start;
        T list[size];
        int end;
        ItemList volatile * volatile next;
    };

    ItemList volatile * volatile start;
    char filler[256];
    ItemList volatile * volatile end;
};

/**
 * Implementation
 * 
 */

#include <stdio.h>

template <typename T, int size> bool HybridQueue<T, size>::Empty()
{
    return (this->start == this->end) && (this->start->start == this->start->end);
}

template <typename T, int size> T HybridQueue<T, size>::Dequeue()
{
    if(this->Empty())
    {
        return NULL;
    }
    if(this->start->start >= size)
    {
        ItemList volatile * volatile old;
        old = this->start;
        this->start = this->start->next;
            delete old;
    }
    T tmp;
    tmp = this->start->list[this->start->start];
    this->start->start++;
    return tmp;
}

template <typename T, int size> void HybridQueue<T, size>::Enqueue(const T& element)
{
    if(this->end->end >= size) {
        this->end->next = new ItemList();
        this->end->next->start = 0;
        this->end->next->list[0] = element;
        this->end->next->end = 1;
        this->end = this->end->next;
    }
    else
    {
        this->end->list[this->end->end] = element;
        this->end->end++;
    }
}

template <typename T, int size> HybridQueue<T, size>::HybridQueue()
{
    this->start = this->end = new ItemList();
    this->start->start = this->start->end = 0;
}

template <typename T, int size> HybridQueue<T, size>::~HybridQueue()
{

}

#endif // HYBRIDQUEUE_H
2 голосов
/ 16 ноября 2011

Насколько я знаю, ни одна библиотека не сделает этого - это слишком сложно. Вы должны будете свернуть свой собственный, и вот идея, которую я только что имел, создать файл журнала для каждого потока, убедиться, что первый элемент в каждой записи является меткой времени, а затем объединить журналы после того, как запустить и отсортировать (по метке времени ), чтобы получить окончательный файл журнала.

Вы можете использовать некоторое локальное хранилище потока (скажем, FILE обрабатывать AFAIK, что не удастся сохранить потоковый объект в локальном хранилище потока), найти этот дескриптор в каждой строке журнала и записать в это файл.

Вся эта сложность против блокировки мьютекса? Я не знаю требований к производительности вашего приложения, но если оно чувствительное - зачем вам регистрироваться (чрезмерно)? Подумайте о других способах получения необходимой информации без регистрации?

Еще одна вещь, которую следует учитывать, - это использовать мьютекс как можно меньше времени, т. Е. Сначала создать свою запись в журнале, а затем непосредственно перед записью в файл получить блокировку.

1 голос
/ 16 ноября 2011

Алгоритмы без блокировки не обязательно самые быстрые. Определите свои границы. Сколько потоков существует для регистрации? Максимум, сколько будет записано в одной операции журнала?

Связанные с вводом / выводом операции намного медленнее, чем переключение контекста потока из-за блокирования / пробуждения потоков. Использование алгоритма без блокировки / вращающейся блокировки с 10 записывающими потоками приведет к большой нагрузке на процессор.

Коротко, блокируйте другие потоки, когда вы пишете в файл.

0 голосов
/ 16 ноября 2011

В программе Windows мы используем пользовательское сообщение Windows. Сначала выделяется память для записи журнала в куче. Затем вызывается PostMessage с указателем в качестве LPARAM и размером записи в качестве WPARAM. Окно получателя извлекает запись, отображает ее и сохраняет в файле журнала. Затем PostMessage возвращается, и выделенная память освобождается отправителем. Этот подход является поточно-ориентированным, и вам не нужно использовать мьютексы. Параллелизм обрабатывается механизмом очереди сообщений Windows. Не очень элегантно, но работает.

...