Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous thread-safe logging in C++ (no mutex)

I'm actually looking for a way to do an asynchronous and thread-safe logging in my C++.

I have already explored thread-safe logging solutions like log4cpp, log4cxx, Boost:log or rlog, but it seems that all of them use a mutex. And as far as I know, mutex is a synchronous solution, which means that all threads are locked as they try to write their messages while other does.

Do you know a solution?

like image 377
Olivier Ramon Avatar asked Nov 16 '11 10:11

Olivier Ramon


3 Answers

I think your statement is wrong: using mutex is not necessary equivalent to a synchronous solution. Yes, Mutex is for synchronization control but it can be used for many different thing. We can use mutex in, for example, a producer consumer queue while the logging is still happening asynchronously.

Honestly I haven't looked into the implementation of these logging library but it should be feasible to make a asynchronous appender (for log4j like lib) which logger writes to an producer consumer queue and another worker thread is responsible to write to a file (or even delegate to another appender), in case it is not provided.


Edit: Just have had a brief scan in log4cxx, it does provide an AsyncAppender which does what I suggested: buffers the incoming logging event, and delegate to attached appender asynchronously.

like image 77
Adrian Shum Avatar answered Oct 31 '22 14:10

Adrian Shum


I'd recomment avoiding the problem by using only one thread for logging. For passing the necessary data to log, you can use lock-free fifo queue (thread safe as long as producer and consumer are strictly separated and only one thread has each role -- therefore you will need one queue for each producer.)

Example of fast lock-free queue is included:

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
like image 24
Erbureth Avatar answered Oct 31 '22 13:10

Erbureth


If I get your question right you are concerned about doing I/O operation (probably write to a file) in a logger's critical section.

Boost:log lets you define a custom writer object. You can define operator() to call async I/O or pass a message to your logging thread (which is doing I/Os).

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

like image 5
Daszek Avatar answered Oct 31 '22 15:10

Daszek