Adding thread safety to a simple registration function?

From what I've read, standard output streams are generally not thread safe . I have a C ++ application (Windows based, using Visual Studio 2005) that has a very simple logging functionality:

void logText(string text)
{
    if(g_OutputLogEnabled && g_OutputLog.is_open())
    {
        string logDate = getDateStamp("%Y-%m-%d %H:%M:%S");
        g_OutputLog << "[" << logDate << "]: " << text << endl;
    }

    cout << text << endl; // Also echo on stdout
}

      

In this example, it g_OutputLog

is instream and g_OutputLogEnabled

is a boolean.

I have been using this little feature in my main application with no problem, but now I want to extend its use to some child threads. These threads run and print data asynchronously as they do this work.

Question . How can I add simple, linear thread safety to this routine? All I really care about is that every line that comes in remains intact in my log. Performance is not a concern in this case, but (as always) the faster it becomes more enjoyable.

I know I can use third party logging packages, but I want to do it myself so I can find out how it works . My multi-threaded knowledge is not what it should be and I am trying to improve it.

I've heard the term critical sections and I'm somewhat familiar with mutexes and semaphores, but which one would I use in this case? Is there a clean, simple solution? Thanks in advance for any advice.

+3


source to share


3 answers


Use blocking, for example:

void logText(string text)
{
    if(g_OutputLogEnabled && g_OutputLog.is_open())
    {
        string logDate = getDateStamp("%Y-%m-%d %H:%M:%S");

        boost::scoped_lock (g_log_mutex);  //lock
        g_OutputLog << "[" << logDate << "]: " << text << endl;

    } //mutex is released automatically here

    boost::scoped_lock (g_cout_log_mutex); //lock on different mutex!
    cout << text << endl; // Also echo on stdout
}

      

Or you can use std::unique_lock

if your compiler supports it.


How to implement scoped_lock

if you can't use Boost, and if you can't std::unique_lock

?



First, define the class mutex

:

#include <Windows.h>

class mutex : private CRITICAL_SECTION  //inherit privately!
{
public:
     mutex() 
     {
        ::InitializeCriticalSection(this);
     }
     ~mutex() 
     {
        ::DeleteCriticalSection(this);
     }
private:

     friend class scoped_lock;  //make scoped_lock a friend of mutex

     //disable copy-semantic 
     mutex(mutex const &);           //do not define it!
     void operator=(mutex const &);  //do not define it!

     void lock() 
     {
        ::EnterCriticalSection(this);
     }
     void unlock() 
     {
        ::LeaveCriticalSection(this);
     }
};

      

Then define scoped_lock

as:

class scoped_lock
{
      mutex & m_mutex;
  public:
      scoped_lock(mutex & m) : m_mutex(m) 
      {
          m_mutex.lock();
      }
      ~scoped_lock()
      {
          m_mutex.unlock();
      }
};

      

You can now use them.

+6


source


Considering the function is clearly not performance oriented, add a simple mutex and lock before writing to threads.

Of course, for security reasons, the lock should be automatically released, so be sure to use RAII.



I would recommend looking at the Boost.Threads library.

0


source


Yes, you should add some sort of protection to your code. You don't want anything exotic, you want to access the resource (your stream) while it can be used by someone else. Two types of sync objects do this well: Critical Sections and Mutex. For more information on blocking, you can start reading this article on Wikipedia . The mutex is usually slower, but can be used for different processes, this is not your case, so you can use a simple critical section to keep your threads synchronized.

A few tips if you don't plan on using a third part library (like the big Boost).

When the EnterCriticalSection function acquires a lock. If a resource is locked by someone else, your thread will be suspended and activated when the resource is freed by its owner (and your locked thread may have higher priority). For short living castles, this may not be the optimal solution , because pause / resume of a thread is time and resource. For this reason, you can set the rotation; before suspending its thread, the OS will consume a bit of time doing nothing on that thread, this can give time to lock the owner to complete its work and release thred. To use this, you must initialize your critical section InitializeCriticalSectionAndSpinCount insteadInitializeCriticalSection .

If you plan on using the critical section, you might consider wrapping everything you need in a class, you will use variable scope to do everything and your code will be clearer (this is just an example, a true implementation cannot be so naive):

class critical_section
{
public:
 critical_section()
 {
  // Here you may use InitializeCriticalSectionAndSpinCount
  InitializeCriticalSection(&_cs);

  // You may not need this behavior, anyway here when you create
  // the object you acquire the lock too
  EnterCriticalSection(&_cs);
 }

 ~critical_section()
 {
   LeaveCriticalSection(&_cs);
   DeleteCriticalSection(&cs);
 }

private:
 CRITICAL_SECTION _cs;
};

      

On Unix / Linux (or you want to be portable) you have to use functions pthread.h

for Mutex.

Blocking may be insufficient

This is only the first step, if your application is logging a lot , you can slow down all threads waiting for the log (don't do anything a priori, validation and profile). If so, you must create a queue, your function logText()

just pushes a new (preformatted) item in the queue and calls PulseEvent on the event signal (created with CreateEvent ). You will have a second thread waiting for this event with WaitForSingleObject , your thread will wake up and it will pop the item from the queue. You may still need a locking mechanism (or you can write your own non-blocking parallel queueto avoid blocking. This solution is faster and it is not. 't use locks of any type, but I think you should do something like this only if you want to learn the topic (threads) generally for a simple log requirement, you don't need to add that kind of complexity.

0


source







All Articles