我正在尝试建立一个模型,在该模型中,我将多次读取整个集合,并对其进行罕见的添加和修改。

我以为我在阅读文档时可能会在.NET中使用ConcurrentBag,因此对于并发读写来说应该是不错的选择。

代码如下所示:

public class Cache
{
   ConcurrentBag<string> cache = new ConcurrentBag<string>();

   // this method gets called frequently
   public IEnumerable<string> GetAllEntries()
   {
         return cache.ToList();
   }

   // this method gets rarely called
   public void Add(string newEntry)
   {
         // add to concurrentBag
   }

   public void Remove(string entryToRemove)
   {
        // remove from concurrent bag
   }
}

但是,我已经反编译了ConcurrentBag类,并在GetEnumerator上始终采用了锁定,这意味着对GetAllEntries的任何调用都将锁定整个集合,并且将无法执行。

我正在考虑解决此问题,并使用列表以这种方式进行编码。
 public class Cache
 {
    private object guard = new object();

    IList<string> cache = new List<string>();

   // this method gets called frequently
   public IEnumerable<string> GetAllEntries()
   {
     var currentCache = cache;
     return currentCache;
   }

   // this method gets rarely called
   public void Add(string newEntry)
   {
       lock (guard)
       {
            cache.Add(newEntry);
       }
   }

   public void Remove(string entryToRemove)
   {
      lock (guard)
      {
           cache.Remove(entryToRemove);
      }
   }
}

由于AddRemove很少被调用,因此我不太在乎锁定对列表的访问。在Get上,我可能会得到列表的陈旧版本,但是我也不在乎,下次请求就可以了。

第二种实现方式是否可行?

编辑

我已经进行了快速性能测试,结果如下:

设置:用10000字符串填充内存中的集合。

行动:GetAllEntries同时50000次。

结果:
00:00:35.2393871使用ConcurrentBag完成操作(第一个实现)00:00:00.0036959使用普通列表完成操作(第二种实现)

代码如下:
 class Program
 {
    static void Main(string[] args)
    {
        // warmup caches and stopwatch
        var cacheWitBag = new CacheWithBag();
        var cacheWitList = new CacheWithList();

        cacheWitBag.Add("abc");
        cacheWitBag.GetAllEntries();

        cacheWitList.Add("abc");
        cacheWitList.GetAllEntries();


        var sw = new Stopwatch();
        // warmup stowtach as well
        sw.Start();

        // initialize caches (rare writes so no real reason to measure here
        for (int i =0; i < 50000; i++)
        {
            cacheWitBag.Add(new Guid().ToString());
            cacheWitList.Add(new Guid().ToString());
        }
        sw.Stop();

        // measure
        var program = new Program();

        sw.Start();
        program.Run(cacheWitBag).Wait();
        sw.Stop();
        Console.WriteLine(sw.Elapsed);

        sw.Restart();
        program.Run2(cacheWitList).Wait();
        sw.Stop();
        Console.WriteLine(sw.Elapsed);
    }

    public async Task Run(CacheWithBag cache1)
    {
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 10000; i++)
        {
            tasks.Add(Task.Run(() => cache1.GetAllEntries()));
        }

        await Task.WhenAll(tasks);
    }
    public async Task Run2(CacheWithList cache)
    {
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 10000; i++)
        {
            tasks.Add(Task.Run(() => cache.GetAllEntries()));
        }

        await Task.WhenAll(tasks);
    }

    public class CacheWithBag
    {
        ConcurrentBag<string> cache = new ConcurrentBag<string>();

        // this method gets called frequently
        public IEnumerable<string> GetAllEntries()
        {
            return cache.ToList();
        }

        // this method gets rarely called
        public void Add(string newEntry)
        {
            cache.Add(newEntry);
        }
    }


    public class CacheWithList
    {
        private object guard = new object();

        IList<string> cache = new List<string>();

        // this method gets called frequently
        public IEnumerable<string> GetAllEntries()
        {
            var currentCache = cache;
            return currentCache;
        }

        // this method gets rarely called
        public void Add(string newEntry)
        {
            lock (guard)
            {
                cache.Add(newEntry);
            }
        }

        public void Remove(string entryToRemove)
        {
            lock (guard)
            {
                cache.Remove(entryToRemove);
            }
        }
    }

}

}

最佳答案

要改善InBetween的解决方案,请执行以下操作:

class Cache
{
   ImmutableHashSet<string> cache = ImmutableHashSet.Create<string>();

   public IEnumerable<string> GetAllEntries()
   {
       return cache;
   }

   public void Add(string newEntry)
   {
       ImmutableInterlocked.Update(ref cache, (set,item) => set.Add(item), newEntry);
   }

   public void Remove(string entryToRemove)
   {
       ImmutableInterlocked.Update(ref cache, (set,item) => set.Remove(item), newEntry);
   }
}

这仅执行原子操作(不锁定),并使用.NET不可变类型。

10-08 05:10