【问题标题】:Trying to find a lock-less solution for a C# concurrent queue试图为 C# 并发队列找到无锁解决方案
【发布时间】:2019-11-09 18:48:03
【问题描述】:

我在 C# 中有以下代码: (_StoreQueue 是一个 ConcurrentQueue)

        var S = _StoreQueue.FirstOrDefault(_ => _.TimeStamp == T);
        if (S == null)
        {
            lock (_QueueLock)
            {
                // try again
                S = _StoreQueue.FirstOrDefault(_ => _.TimeStamp == T);
                if (S == null)
                {
                    S = new Store(T);
                    _StoreQueue.Enqueue(S);
                }
            }
        }

系统正在实时收集数据(相当高的频率,大约 300-400 次调用/秒)并将其放入代表 5 秒间隔的 bin(存储对象)中。这些 bin 在写入时位于队列中,而在处理和写入数据时队列会清空。

因此,当数据到达时,会检查是否有该时间戳的 bin(以 5 秒舍入),如果没有,则创建一个。

由于这是非常多线程的,系统遵循以下逻辑:

如果有bin,就是用来放数据的。 如果没有 bin,则会启动一个锁,并在该锁内再次进行检查以确保它不是同时由另一个线程创建的。如果仍然没有 bin,则创建一个。

用这个系统,锁大概每2k调用一次

我正在尝试看看是否有办法解除锁定,但这主要是因为我认为必须有一个比仔细检查更好的解决方案。

我一直在考虑的另一种方法是提前创建空箱,这将完全消除对任何锁的需求,但是搜索正确的箱会变得更慢,因为它必须扫描预先构建的列表垃圾箱以找到合适的垃圾箱。

【问题讨论】:

  • System.Collections.Concurrent.ConcurrentQueue 不起作用?已经有并发队列数据结构了。
  • 这是一个并发队列,但问题是如果2个线程同时尝试写入,一个可能正在创建一个bin,而另一个的bin检查失败(它会创建一个bin也);我刚刚编辑了问题以说明这是一个 ConcurrentQueue,因为这不清楚。
  • 您可以使用 ConcurrentQueue,但在排队之前不要聚合 Store 中的数据。如果您让队列数据的使用者将数据聚合成 5 秒的数据包,您的锁定问题就会消失。 (假设您收集的数据量不会造成过度的内存压力)
  • 你为什么还要使用ConcurrentQueueConcurrentDictionary 将真正解决您的问题,并且只使用 1 行。键是Store.TimeStamp 的值类型,值是Store 对象
  • elgonzo 的解决方案可行,但它需要在各处进行相当多的更改。字典不会导致类似的问题:一个线程找不到键,创建存储,在创建并添加到字典之前,另一个线程找不到键并自己制作?跨度>

标签: c# concurrency locking


【解决方案1】:

使用ConcurrentDictionary 可以解决您遇到的问题。在这里,我假设您的 TimeStamp 属性的类型为 double,但它可以是任何类型,只要您使 ConcurrentDictionary 键与类型匹配。

class Program
{
    ConcurrentDictionary<double, Store> _StoreQueue = new ConcurrentDictionary<double, Store>();

    static void Main(string[] args)
    {
        var T = 17d;

        // try to add if not exit the store with 17
        _StoreQueue.GetOrAdd(T, new Store(T));
    }
    public class Store
    {
        public double TimeStamp { get; set; }
        public Store(double timeStamp)
        {
            TimeStamp = timeStamp;
        }
    }
}

【讨论】:

  • 一个值得一提的细节是new Store(T) 部分(如果您使用溢出,甚至是委托工厂方法)可能会在两个线程竞争时连续调用两次。解决它的一种方法是使用Lazy&lt;Store&gt; 而不是Store(或者,使用双重检查锁定),类似于this thread。当然,这只是在实例化对象成本高昂的情况下才会出现问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多