最有效的多写锁单读并发模型
本文关键字:单读 并发 模型 写锁 有效 | 更新日期: 2023-09-27 18:06:17
所以我有许多线程为我提供输入数据,这些数据必须按到达的顺序由单个线程处理。目前,所有的输入项都被插入到队列中,并且对队列的读/写都受到c#锁语句的保护。然而,随着时间的推移,应用程序的CPU使用率上升到不可接受的水平,并且分析器表示大部分CPU时间都花在了锁语句本身上。是否有一种更有效的同步方法可以代替锁,支持多个写器和一个读器?
听起来像是写程序在相互争夺锁。考虑一个模型,其中每个写入器都有自己的队列,读取器使用Peek方法从每个队列读取第一条消息,而不删除它。然后,读取器可以在队列之间不断迭代,从每个队列的第一项集合中查看第一项,然后删除并处理该第一项。它将比您当前的体系结构慢,但应该消除写入器之间的锁争用。
一个简单的例子如下:
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());
}
}
}
如果您正在使用。net版本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),然后你可以让你的writer线程向该队列写入它们的核心内容。你的读者可以在准备好了的时候把这些东西摘下来。如果你的大部分cpu时间都花在队列周围的锁上(我假设你实际上并没有锁定队列上的工作),那么将队列放在应用程序的外部可能会有所帮助。理想情况下,您还可以将写入和读取拆分为单独的进程。
另一件要检查的事情是,你锁定的对象没有被用来锁定应用程序中的其他地方。监视器(锁语句后面的东西)可能是最轻量级的线程同步方法,所以最好重新架构一些东西,以避免在处理项目的同一进程中锁定。