Беззащитная многопоточная очередь - нужен совет - PullRequest
2 голосов
/ 12 января 2010

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

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

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

Ответы [ 3 ]

4 голосов
/ 12 января 2010

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

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

Я бы порекомендовал "Очередь блокировки" с этой страницы .

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

Что касается второй части вашего вопроса, это зависит от того, что именно представляет собой «какое-то событие синхронизации». Если вы собираетесь использовать вызов метода, тогда позвольте этому запустить одноразовый поток. Если вы хотите подождать на семафоре, чем , не используйте Monitor and Pulse. Они не надежны здесь. Использовать 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
            }
        } 
    }
}

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

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

3 голосов
/ 12 января 2010

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

1 голос
/ 12 января 2010

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

Функция журнала:
1. Локально подготовить элемент журнала (узел со строкой регистрации).
2. Установите следующий указатель локального узла на head .
3. ATOMIC: Сравнить head со следующим локальным узлом, если он равен, заменить head на адрес локального узла.
4. Если операция завершилась неудачно, повторите процедуру, начиная с шага 2, в противном случае элемент находится в «очереди».

рабочий:
1. Скопируйте head local.
2. ATOMIC: Сравнить головка с локальной, если она равна, заменить головка на NULL.
3. Если операция не удалась, повторите с шага 1.
4. Если это удалось, обработайте элементы; которые теперь являются локальными и находятся вне очереди.

...