无锁线程安全队列-需要建议

Cap*_*mic 2 c# logging thread-safety lock-free

我需要设计一个线程安全的记录器。我的记录器必须具有一个Log()方法,该方法只是将要记录的文本排队。另外,记录器必须是无锁的-这样其他线程就可以在不锁定记录器的情况下记录消息。我需要设计一个工作线程,该线程必须等待一些同步事件,然后使用标准的.NET日志记录(不是线程安全的)记录队列中的所有消息。因此,我感兴趣的是工作线程和Log函数的同步。下面是我设计的课程的草图。我想我必须在这里使用Monitor.Wait / Pulse或任何其他方式来挂起和恢复工作线程。我不想在没有记录器的工作时花费CPU周期。

让我换一种说法-我想设计一个不会阻止使用它的调用者线程的记录器。我有一个高性能的系统-这是必需的。

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
 }
}    
}
Run Code Online (Sandbox Code Playgroud)

Hen*_*man 5

“无锁”并不意味着线程不会互相阻塞。这意味着它们通过非常有效但又非常棘手的机制相互阻止。仅在非常高性能的情况下才需要,甚至专家都会弄错(很多)。

最佳建议:忘记“无锁”,而只使用“线程安全”队列。

我会推荐此页面上的“阻止队列” 。

ThreadRoutine(消费者)包括在类本身是一个选择问题。

在问题的第二部分,它取决于确切的“某些同步事件”。如果要使用Method调用,则让该线程启动单发线程。如果您要等待信号量,则不要 使用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
            }
        } 
    }
}
Run Code Online (Sandbox Code Playgroud)

讨论的一部分似乎是处理Queue(标记为***)时的处理方式。您可以锁定队列并处理所有项目,在此期间,新条目的添加将被阻止(较长),或者一次又一次地锁定和检索条目,并且每次仅锁定(非常)一次。我已经添加了最后一种情况。

总结:您不想要无锁解决方案,而是无块解决方案。无障碍不存在,您将不得不解决一些尽可能少的障碍。mys示例的最后一次迭代(未完成)显示了如何仅锁定Enqueue和Dequeue调用。我认为那将足够快。

  • 很少有数据结构可以安全地从多个线程更新而没有同步或锁定结构,这种情况很少见。无锁只是意味着您不使用OS内核锁对象,而是使用诸如spinwaits之类的东西来等待事情变得可用,通常用在根本没有这种锁的内核代码中。我将从简单的事情开始,例如普通的阻塞队列,然后看看它是如何扩展的。 (4认同)
  • 阻塞还不错。这只是意味着您的线程只有在您明确将其唤醒后才能执行。在这种情况下,您希望在每次记录内容时通知记录程序线程。为什么要使用无锁队列?除了同步入队/出队外,您还需要进行更多同步。使用无锁队列根本不会给您带来明显的性能提升。 (2认同)