我有许多线程向我提供输入数据,必须按照到达顺序由单个线程处理。目前,所有的输入项都被插入到一个队列中,并且对队列的读/写操作受到C#锁语句的保护。然而,随着时间的推移,应用程序的CPU使用率上升到不可接受的水平,并且分析器显示大部分CPU时间都花在了锁语句本身上。是否有更有效的同步方法可以替代锁,支持多个写入者和一个读取者?
我有许多线程向我提供输入数据,必须按照到达顺序由单个线程处理。目前,所有的输入项都被插入到一个队列中,并且对队列的读/写操作受到C#锁语句的保护。然而,随着时间的推移,应用程序的CPU使用率上升到不可接受的水平,并且分析器显示大部分CPU时间都花在了锁语句本身上。是否有更有效的同步方法可以替代锁,支持多个写入者和一个读取者?
public class TimestampedItem<T> : IComparable<TimestampedItem<T>>
{
public DateTime TimeStamp { get; set; }
public T Data { get; set; }
public int CompareTo(TimestampedItem<T> other)
{
return TimeStamp.CompareTo(other.TimeStamp);
}
}
public void ReadFirstFromEachQueue<T>(IEnumerable<Queue<TimestampedItem<T>>> queues)
{
while (true)
{
var firstItems = new List<TimestampedItem<T>>(queues.Select(q => { lock (q) { return q.Peek(); } }));
ProcessItem(firstItems.OrderBy(tsi => tsi.TimeStamp).First());
}
}
}
4.0
,则可以使用 ConcurrentQueue
(它是 ConcurrentCollections
的一部分)代替普通的 Queue
,并在读/写队列数据时消除锁定。 ConcurrentCollections
旨在用于处理具有无锁代码的并发读/写。4.0
,则可以仅在没有其他锁定时锁定,这可以通过使用 Monitor.TryEnter
而不是 lock
来实现。请注意,lock
本身是 Monitor.Enter
和 Monitor.Exit
的组合。示例实现如下:private readonly object _syncObject = new object();
private bool TryUpdate(object someData)
{
if (Monitor.TryEnter(_syncObject))
{
try
{
//Update the data here.
return true;
}
finally
{
Monitor.Exit(_SyncObject);
}
}
return false;
}
这可能对您的应用程序是一个很大的改变,但您可以考虑将队列外部化到应用程序之外(例如 MSMQ),然后您的写入线程可以尽情地向该队列写入。当准备好时,读取器只需取出项目即可。如果您的大部分 CPU 时间仅用于锁定队列周围(我假设您实际上没有在放置在队列上的项目上进行锁定),那么将队列放置在应用程序之外可能会真正有所帮助。理想情况下,您还可以将编写和阅读拆分为单独的进程。
另一件要检查的事情是,您正在锁定的对象是否被用于在应用程序中的其他位置进行锁定。监视器(锁语句背后的东西)可能是最轻量级的线程同步方法,因此最好重新设计架构以避免在执行项目处理的同一进程中进行锁定。
lock
正在使用,第一个"Monitor.TryEnter
"不会阻塞等待进入,而是会返回false,而不是在该行等待它被释放。 - Jalal Said