Добавление безопасности потока к простой функции ведения журнала? - PullRequest
3 голосов
/ 25 марта 2012

Из того, что я прочитал, стандартные выходные потоки, как правило, не безопасны для потоков .У меня есть приложение на C ++ (на основе Windows, с использованием Visual Studio 2005), которое имеет очень простую функцию ведения журнала:

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
}

В этом примере g_OutputLog - это ofstream, а g_OutputLogEnabled - логическое значение.

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

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

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

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

Ответы [ 3 ]

6 голосов
/ 25 марта 2012

Используйте блокировку с ограничением, например:

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
}

Или вы можете использовать std::unique_lock, если ваш компилятор поддерживает это.


Как бы вы реализовали scoped_lock, если вы не можете использовать Boost и если у вас нет std::unique_lock?

Первое определение 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);
     }
};

Затем определите scoped_lock как:

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

Теперь вы можете использовать их.

0 голосов
/ 25 марта 2012

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

Несколько советов, есливы не планируете использовать библиотеку третьей части (как, например, отличный Boost).

Когда EnterCriticalSection функция для получения блокировки.Если ресурс заблокирован кем-то другим, ваш поток будет приостановлен и повторно активирован, когда ресурс будет освобожден его владельцем (более того, ваш заблокированный поток может повысить его приоритет). Для кратковременных блокировок это не может быть оптимальным решением , потому что приостановка / возобновление потока требует много времени и ресурсов.По этой причине вы можете установить spin ;перед приостановкой вашего потока ОС будет тратить немного времени, ничего не делая в этом потоке, это может дать время для блокировки владельца, чтобы завершить свою работу и освободить поток.Чтобы использовать это, вы должны инициализировать ваш критический раздел с InitializeCriticalSectionAndSpinCount вместо InitializeCriticalSection .

Если вы планируете использовать критический раздел, вы можете подумать, чтобы обернуть все необходимоев классе вы будете использовать переменную scope для всего, и ваш код будет более понятным (это всего лишь пример, истинная реализация не может быть такой наивной):

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;
};

в Unix / Linux (или вы хотите быть переносимым) вам следует использовать функции pthread.h для Mutex.

Блокировки может быть недостаточно

Это только первый шаг, если ваше приложение регистрируетЛот Вы можете замедлить все потоки, ожидающие регистрации (не делайте ничего априори, проверьте и профиль).Если это так, вы должны создать очередь, ваша logText() функция просто помещает новый (предварительно отформатированный) элемент в очередь и вызывает PulseEvent , чтобы сообщить о событии (созданном с помощью CreateEvent ).У вас будет второй поток, ожидающий этого события с WaitForSingleObject , ваш поток проснется и извлечет элемент из очереди.Вам по-прежнему может понадобиться какой-то механизм блокировки (или вы можете написать собственную неблокирующую параллельную очередь , чтобы избежать какой-либо блокировки. Это решение быстрее и не использует никаких блокировок, но я думаю, что вам следует это сделатьчто-то подобное, только если вы хотите изучить тему (темы), обычно для простого требования к журналу вам не нужно добавлять такую ​​сложность.

0 голосов
/ 25 марта 2012

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

Конечно, из соображений безопасности блокировка должна быть автоматически снята, поэтому обязательно используйте RAII.

Я бы рекомендовал взглянуть на библиотеку Boost.Threads.

...