Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 Thread-safe polymorphism with less verbosity

I am writing a logger and would like to make it thread-safe. I have done so by doing the following:

class Logger
{
public:
    virtual ~Logger();

    LogSeverity GetSeverity() const;
    void SetSeverity(LogSeverity s);
protected:
    std::mutex mutex;
private:
    LogSeverity severity;
};

void Logger::SetSeverity(LogSeverity s)
{
    std::lock_guard<std::mutex> lock(mutex);
    severity = s;
}

LogSeverity Logger::GetSeverity() const
{
    std::lock_guard<std::mutex> lock(mutex);
    return severity;
}

void Logger::SetSeverity(LogSeverity s) const
{
    std::lock_guard<std::mutex> lock(mutex);
    severity = s;
}

// StreamLogger inherits from Logger

void StreamLogger::SetStream(ostream* s)
{
    std::lock_guard<std::mutex> lock(mutex);
    stream = s;
}

ostream* StreamLogger::GetStream() const
{
    std::lock_guard<std::mutex> lock(mutex);
    return stream;
}

However, all public access to the class require this extremely redundant lock.

Two options I see:

1) Caller of these public function will lock the whole object using the mutex within the class

Logger l = new Logger();
std::lock_guard<std::mutex> lock(l->lock());
l->SetSeverity(LogDebug);

2) Wrapper lock around each variable in the class

template typename<T> struct synchronized
{
public:    
    synchronized=(const T &val);
    // etc..
private:
    std::mutex lock;
    T v;
};

class Logger
{
private:
    synchronized<LogSeverity> severity;
};

However, this solution is very resource intensive, lock for each item.

Am I on the right track or is there something I am missing?

like image 311
cvicci Avatar asked May 29 '26 04:05

cvicci


1 Answers

First of all, you need to carefully reconsider possible use cases:

  • do you really need logger to be so configurable?
  • what properties can be initialized during construction?
  • does it make sense to change all of them?

I have a strange feeling, that you think about your classes in a very small perspective: "well, it's a logger, so I will put all possibly useful features into it" (I may be wrong). Classes should have complete, but minimal interfaces, explicitly representing what particular class is responsible for. Think about that.

As for your multi-threading problem: I don't think shared logger is a good idea. Personally, I always prefer thread-specific primitives in such cases (one logger per thread). Why?

  • if logger writes to a section of memory, you only need to lock block of memory, not logger itself
  • if logger writes to a file, your task is even simpler - remember, that OS manages file access, so you don't need to worry about two loggers writing to the same section of the same file (you must design your logger to ensure that, but it's really not that hard)
  • bonus: different threads may write to different outputs if necessary

If your compiler supports C++ 11, above solution is basically proper usage of thread_local, __declspec(thread) or __thread, depending on what your compiler supports.

If you still want to implement shared logger, start from design review. For example: are you sure, that changing single property requires locking a mutex? Things like severity member is perfect candidate for std::atomic. It may require more work, but can be much faster.

class Logger
{
    //cut

private:
    std::atomic<LogSeverity> severity;
};

void Logger::SetSeverity(LogSeverity s)
{
    severity.store(s, std::memory_order_release);
}

LogSeverity Logger::GetSeverity() const
{
    return severity.load(std::memory_order_acquire);
}

std::memory_order_acquire/release is just an example - you may want to use stronger ordering like memory_order_seq_cst (if you need total global ordering). However acquire/release pair is usually enough to ensure proper synchronization between loads and stores and small bonus - they will not produce any fences on x86.

If think you might want to read C++ Concurrency in Action by Anthony Williams. It is the best resource for learning threads, atomics, synchronization, memory orderings and more.

There is also a very good articles on Bartosz Milewski's blog. Like this one: C++ atomics and memory ordering .

If you are not familiar with topics like atomics, fences, orderings etc., these resources are very good to start with.

like image 174
Mateusz Grzejek Avatar answered May 31 '26 18:05

Mateusz Grzejek



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!