C# ConcurrentBag的性能,多次读取,很少修改

C# ConcurrentBag的性能,多次读取,很少修改,c#,.net,multithreading,thread-safety,C#,.net,Multithreading,Thread Safety,我正在尝试建立一个模型,在这个模型中,我将对整个收藏进行多次读取,并对其进行罕见的添加和修改 我想我可能会在.NET中使用ConcurrentBag,因为我已经阅读了文档,它应该适合并发读写 代码如下所示: public class Cache { ConcurrentBag<string> cache = new ConcurrentBag<string>(); // this method gets called frequently

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

我想我可能会在.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
   } 
} 
由于
Add
Remove
很少被调用,因此我不太在意锁定对列表的访问。在
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);
            }
        }
    }

}
类程序
{
静态void Main(字符串[]参数)
{
//预热缓存和秒表
var cacheWitBag=新的CacheWithBag();
var cacheWitList=新的CacheWithList();
添加(“abc”);
cacheWitBag.GetAllEntries();
cacheWitList.添加(“abc”);
cacheWitList.GetAllEntries();
var sw=新秒表();
//预热斯托塔克也一样
sw.Start();
//初始化缓存(很少写入,因此没有真正的理由在这里进行测量
对于(int i=0;i<50000;i++)
{
cacheWitBag.Add(新Guid().ToString());
cacheWitList.Add(新Guid().ToString());
}
sw.Stop();
//量
var program=新程序();
sw.Start();
program.Run(cacheWitBag.Wait();
sw.Stop();
控制台写入线(软件运行时间);
sw.Restart();
program.Run2(cacheWitList.Wait();
sw.Stop();
控制台写入线(软件运行时间);
}
公共异步任务运行(CacheWithBag cache1)
{
列表任务=新列表();
对于(int i=0;i<10000;i++)
{
tasks.Add(Task.Run(()=>cache1.GetAllEntries());
}
等待任务。何时(任务);
}
公共异步任务Run2(CacheWithList缓存)
{
列表任务=新列表();
对于(int i=0;i<10000;i++)
{
tasks.Add(Task.Run(()=>cache.GetAllEntries());
}
等待任务。何时(任务);
}
公营邮袋
{
ConcurrentBag缓存=新ConcurrentBag();
//这个方法经常被调用
公共IEnumerable GetAllEntries()
{
返回cache.ToList();
}
//这个方法很少被调用
公共void Add(字符串newEntry)
{
cache.Add(newEntry);
}
}
公共类缓存列表
{
私有对象保护=新对象();
IList cache=new List();
//这个方法经常被调用
公共IEnumerable GetAllEntries()
{
var currentCache=cache;
返回当前缓存;
}
//这个方法很少被调用
公共void Add(字符串newEntry)
{
锁(护罩)
{
cache.Add(newEntry);
}
}
公共无效删除(字符串入口删除)
{
锁(护罩)
{
cache.Remove(entryToRemove);
}
}
}
}

}/P>

在您当前的场景中,<代码>添加<代码> >代码>删除< /代码>很少被调用,我会考虑以下方法:

public class Cache 
{     
    private object guard = new object();
    var cache = new SomeImmutableCollection<string>();

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

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

   public void Remove(string entryToRemove)
   {
      lock (guard) 
      {
          cache = cache.Remove(entryToRemove);
      }
   } 
}
公共类缓存
{     
私有对象保护=新对象();
var cache=new SomeImmutableCollection();
//这个方法经常被调用
公共IEnumerable GetAllEntries()
{   
返回缓存;
}
//这个方法很少被调用
公共void Add(字符串newEntry)
{  
锁(护罩)
{
cache=cache.Add(newEntry);
}
}
公共无效删除(字符串入口删除)
{
锁(护罩)
{
cache=cache.Remove(entryToRemove);
}
} 
}
这里的根本变化是,
cache
现在是一个不可变的集合,这意味着它永远无法更改。因此集合本身的并发性问题就消失了,无法更改的东西本质上是线程安全的

此外,取决于如何调用<代码>添加< <代码> >代码>删除< /代码>,您甚至可以考虑删除它们中的锁,因为它现在所做的是避免在<代码>添加<代码> > <代码>删除>代码>和缓存更新的潜在损失之间。如果这种情况非常不可能,您可以侥幸逃脱。说,我非常怀疑几毫微秒,一个没有竞争的锁是一个相关的因素在这里实际考虑;


< > >代码>某些不稳定的收集< /代码>可以是在<代码> Stase.Sypto.不可变的< /代码>中找到的任何一个集合,它更适合您的需要.

在您当前的场景中,<>代码>添加< /> >和<代码>删除>代码>很少被调用,我将考虑以下方法:

public class Cache 
{     
    private object guard = new object();
    var cache = new SomeImmutableCollection<string>();

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

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

   public void Remove(string entryToRemove)
   {
      lock (guard) 
      {
          cache = cache.Remove(entryToRemove);
      }
   } 
}
公共类缓存
{     
私有对象保护=新对象();
var cache=new SomeImmutableCollection();
//这个方法经常被调用
公共IEnumerable GetAllEntries()
{   
返回缓存;
}
//这个方法很少被调用
公共void Add(字符串newEntry)
{  
锁(护罩)
{
cache=cache.Add(newEntry);
}
}
公共无效删除(字符串入口删除)
{
锁(护罩)
{
cache=cache.Remove(entryToRemove);
}
}
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);
   }
}