Потокобезопасная очередь без блокировок - нужен совет

StackOverflow https://stackoverflow.com/questions/2048785

Вопрос

Мне нужно разработать потокобезопасный регистратор.Мой регистратор должен иметь метод Log (), который просто ставит текст в очередь для регистрации.Также регистратор должен быть свободен от блокировки - чтобы другой поток мог регистрировать сообщения без блокировки регистратора.Мне нужно создать рабочий поток, который должен ожидать некоторого события синхронизации, а затем регистрировать все сообщения из очереди, используя стандартное ведение журнала .NET (это не потокобезопасно).Итак, что меня интересует, так это синхронизация функции рабочего потока и журнала.Ниже приведен эскиз класса, который я разработал.Я думаю, что я должен использовать Monitor.Подождите / Пульсируйте здесь или любым другим способом приостановить и возобновить рабочий поток.Я не хочу тратить циклы процессора, когда нет задания для регистратора.

Позвольте мне выразить это по-другому - я хочу разработать регистратор, который будет не блокировать вызывающий поток, который его использует.У меня высокопроизводительная система - и это обязательное условие.

class MyLogger
{
  // This is a lockfree queue - threads can directly enqueue and dequeue
  private LockFreeQueue<String> _logQueue;
  // worker thread
  Thread _workerThread;
  bool _IsRunning = true;

 // this function is used by other threads to queue log messages
  public void Log(String text)
{
  _logQueue.Enqueue(text);
}

// this is worker thread function
private void ThreadRoutine()
{
 while(IsRunning)
 {
   // do something here
 }
}    
}
Это было полезно?

Решение

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

Лучший совет:забудьте о "безблокировочной" и просто используйте "потокобезопасную" очередь.

Я бы порекомендовал "Блокирующую очередь" из эта страница.

И это вопрос выбора, включать ли ThreadRoutine (потребитель) в самом классе.

Что касается второй части вашего вопроса, то это зависит от того, что именно представляет собой "некоторое событие синхронизации".Если вы собираетесь использовать вызов метода, то пусть это запустит одноразовый поток.Если вы хотите подождать на семафоре, чем не надо используйте монитор и пульс.Здесь на них нельзя положиться.Используйте AutoResetEvent/ManualResetEvent.
Как это сделать, зависит от того, как вы хотите это использовать.

Ваши основные ингредиенты должны выглядеть примерно так:

class Logger
{
    private AutoResetEvent _waitEvent = new AutoResetEvent(false);
    private object _locker = new object();
    private bool _isRunning = true;    

    public void Log(string msg)
    {
       lock(_locker) { _queue.Enqueue(msg); }
    }

    public void FlushQueue()
    {
        _waitEvent.Set();
    }

    private void WorkerProc(object state)
    {
        while (_isRunning)
        {
            _waitEvent.WaitOne();
            // process queue, 
            // ***
            while(true)
            {
                string s = null;
                lock(_locker)
                {
                   if (_queue.IsEmpty) 
                      break;
                   s = _queue.Dequeu();
                }
                if (s != null)
                  // process s
            }
        } 
    }
}

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

Краткое содержание:Вам нужно не решение без блокировок, а решение без блоков.Безблоковой системы не существует, вам придется довольствоваться чем-то, что блокирует как можно меньше.Последняя итерация моего примера (неполная) показывает, как блокировать только вызовы Enqueue и Dequeue.Я думаю, что это будет достаточно быстро.

Другие советы

Показал ли вам ваш профилировщик, что вы испытываете большие накладные расходы, используя простой lock заявление?Программирование без блокировок очень сложно сделать правильно, и если вам это действительно нужно, я бы посоветовал взять что-то существующее из надежного источника.

Нетрудно сделать это без блокировки, если у вас есть атомарные операции.Возьмем односвязный список;вам просто нужно голова указатель.

Функция ведения журнала:
1.Локально подготовьте элемент журнала (узел со строкой ведения журнала).
2.Установите следующий указатель локального узла на голова.
3. АТОМНЫЙ: Сравнить голова на следующий локальный узел, если он равен, замените голова с адресом локального узла.
4.Если операция завершилась неудачей, повторите шаг 2, в противном случае элемент находится в "очереди".

Рабочий:
1.Копировать голова локально.
2. АТОМНЫЙ: Сравнить голова на локальный, если он равен, замените голова с нулевым значением.
3.Если операция завершилась неудачей, повторите шаг 1.
4.Если это удалось, обработайте элементы;которые теперь являются локальными и находятся вне "очереди".

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top