最有效的多写锁单读并发模型

本文关键字:单读 并发 模型 写锁 有效 | 更新日期: 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.EnterMonitor.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时间都花在队列周围的锁上(我假设你实际上并没有锁定队列上的工作),那么将队列放在应用程序的外部可能会有所帮助。理想情况下,您还可以将写入和读取拆分为单独的进程。

另一件要检查的事情是,你锁定的对象没有被用来锁定应用程序中的其他地方。监视器(锁语句后面的东西)可能是最轻量级的线程同步方法,所以最好重新架构一些东西,以避免在处理项目的同一进程中锁定。