我需要设计一个线程安全的记录器。我的记录器必须有一个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
}
}
}
答案 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.如果成功,处理项目;现在是本地的,并且在“队列”之外。