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

时间:2010-01-12 12:17:06

标签: c# logging thread-safety lock-free

我需要设计一个线程安全的记录器。我的记录器必须有一个Log()方法,它只是将要记录的文本排队。记录器也必须是无锁的 - 这样其他线程就可以在不锁定记录器的情况下记录消息。我需要设计一个必须等​​待的工作线程 对于某些同步事件,然后使用标准.NET日志记录(不是线程安全的)记录队列中的所有消息。所以我感兴趣的是工作线程和日志功能的同步。下面是我设计的课程草图。我想我必须在这里使用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
 }
}    
}

3 个答案:

答案 0 :(得分:4)

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

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

我会推荐this page中的“阻止队列”。

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

对于问题的第二部分,它取决于“某些同步事件”究竟是什么。如果您打算使用Method调用,那么让它启动一次性线程。如果你想等待信号量而不是使用Monitor和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调用。我认为这将足够快。

答案 1 :(得分:3)

您的探查器是否通过使用简单的lock声明向您显示您遇到了大量开销?无锁编程很难做到正确,如果你真的需要它,我会建议从可靠的来源获取现有的东西。

答案 2 :(得分:1)

如果你有原子操作,那么这个锁是不难的。拿一个单链表;你只需要指针。

记录功能:
1.本地准备日志项(带有日志字符串的节点) 2.将本地节点的下一个指针设置为 head 3. ATOMIC: head 与本地节点的下一个进行比较,如果相等,则将 head 替换为本地节点的地址。
4.如果操作失败,请从步骤2开始重复,否则,该项目位于“队列”中。

工人:
1.在本地复制 2. ATOMIC: head 与本地值进行比较,如果相等,请将 head 替换为NULL。
3.如果操作失败,请从步骤1开始重复 4.如果成功,处理项目;现在是本地的,并且在“队列”之外。