简体   繁体   English

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

[英]Lock-free thread-safe queue - need advice

I need to design a thread-safe logger. 我需要设计一个线程安全的记录器。 My logger must have a Log() method that simply queues a text to be logged. 我的记录器必须有一个Log()方法,它只是将要记录的文本排队。 Also a logger must be lock-free - so that other thread can log messages without locking the logger. 记录器也必须是无锁的 - 这样其他线程就可以在不锁定记录器的情况下记录消息。 I need to design a worker thread that must wait for some synchronization event and then log all messages from the queue using standard .NET logging (that is not thread-safe). 我需要设计一个必须等​​待某个同步事件的工作线程,然后使用标准.NET日志记录(这不是线程安全的)记录来自队列的所有消息。 So what i am interested in is synchronization of worker thread - and Log function. 所以我感兴趣的是工作线程和日志功能的同步。 Below is a sketch of the class that i designed. 下面是我设计的课程草图。 I think I must use Monitor.Wait/Pulse here or any other means to suspend and resume worker thread. 我想我必须在这里使用Monitor.Wait / Pulse或任何其他方法来暂停和恢复工作线程。 I don;t want to spend CPU cycles when there is no job for logger. 我不想在没有记录器工作的情况下花费CPU周期。

Let me put it another way - I want to design a logger that will not block a caller threads that use it. 让我换一种说法 - 我想设计一个不会阻塞使用它的调用者线程的记录器。 I have a high performance system - and that is a requirement. 我有一个高性能系统 - 这是一个要求。

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

"lock-free"does not mean that threads won't block each other. “无锁”并不意味着线程不会相互阻塞。 It means that they block each other through very efficient but also very tricky mechanisms. 这意味着它们通过非常有效但非常棘手的机制相互阻挡。 Only needed for very high performance scenarios and even the experts get it wrong (a lot). 只有非常高性能的情况才需要,甚至专家也会错误(很多)。

Best advice: forget "lock-free"and just use a "thread-safe" queue. 最好的建议:忘记“无锁”,只使用“线程安全”队列。

I would recommend the "Blocking Queue" from this page . 我会从这个页面推荐“阻止队列”。

And it's a matter of choice to include the ThreadRoutine (the Consumer) in the class itself. 在类本身中包含ThreadRoutine (Consumer)是一个选择问题。

To the second part of your question, it depends on what "some synchronization event" exactly is. 对于问题的第二部分,它取决于“某些同步事件”究竟是什么。 If you are going to use a Method call, then let that start a one-shot thread. 如果您打算使用Method调用,那么让它启动一次性线程。 If you want to wait on a Semaphore than don't use Monitor and Pulse. 如果你想等待信号量而不是使用Monitor和Pulse。 They are not reliable here. 他们在这里不可靠。 Use an AutoResetEvent/ManualResetEvent. 使用AutoResetEvent / ManualResetEvent。
How to surface that depends on how you want to use it. 如何表面取决于你想如何使用它。

Your basic ingredients should look like this: 您的基本成分应如下所示:

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

Part of the discussion seems to be what to do when processing the Queue (marked *** ). 部分讨论似乎是在处理队列时要做的事情(标记为*** )。 You can lock the Queue and process all items, during which adding of new entries will be blocked (longer), or lock and retrieve entries one by one and only lock (very) shortly each time. 您可以锁定队列并处理所有项目,在此期间将阻止添加新条目(更长),或者逐个锁定和检索条目,并且每次只能锁定(非常)。 I've adde that last scenario. 我加上最后一个场景。

A summary: You don't want a Lock-Free solution but a Block-Free one. 摘要:您不需要无锁解决方案,而是无块解决方案。 Block-Free doesn't exist, you will have to settle for something that blocks as little as possible. Block-Free不存在,你将不得不满足于尽可能少的阻塞。 The last iteration of mys sample (incomplete) show how to only lock around the Enqueue and Dequeue calls. mys sample的最后一次迭代(不完整)显示了如何仅锁定Enqueue和Dequeue调用。 I think that will be fast enough. 我认为这将足够快。

Has your profiler shown you that you are experiencing a large overhead by using a simple lock statement? 您的探查器是否通过使用简单的lock语句向您显示您正在经历大量开销? Lock-free programming is very hard to get right, and if you really need it I would suggest taking something existing from a reliable source. 无锁编程很难做到,如果你真的需要它,我会建议从可靠的来源获取现有的东西。

It's not hard to make this lock-free if you have atomic operations. 如果你有原子操作,这不难做到无锁。 Take a singly linked list; 拿一个单链表; you just need the head pointer. 你只需要指针。

Log function: 记录功能:
1. Locally prepare the log item (node with logging string). 1.本地准备日志项(带有日志字符串的节点)。
2. Set the local node's next pointer to head . 2.将本地节点的下一个指针设置为head
3. ATOMIC: Compare head with local node's next, if equal, replace head with address of local node. 3. 原子:与本地节点的下一个比较,如果相等,则用本地节点的地址替换
4. If the operation failed, repeat from step 2, otherwise, the item is in the "queue". 4.如果操作失败,请从步骤2开始重复,否则,该项目位于“队列”中。

Worker: 工人:
1. Copy head locally. 1.在本地复制头部
2. ATOMIC: Compare head with local one, if equal, replace head with NULL. 2. 原子:与本地比较,如果相等,用NULL替换
3. If the operation failed, repeat from step 1. 3.如果操作失败,请从步骤1开始重复。
4. If it succeeded, process the items; 4.如果成功,处理项目; which are now local and out of the "queue". 现在是本地的,并且在“队列”之外。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM