ConcurrentBag的性能,多次读取,罕见的修改

Dan*_*inu 1 .net c# multithreading thread-safety

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

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

代码如下所示:

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
   } 
} 
Run Code Online (Sandbox Code Playgroud)

但是,我已经对该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);
      }
   } 
} 
Run Code Online (Sandbox Code Playgroud)

由于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);
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

}

Cor*_*son 5

要改善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);
   }
}
Run Code Online (Sandbox Code Playgroud)

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