【问题标题】:ConcurrentBag<MyType> Vs List<MyType>ConcurrentBag<MyType> 与 List<MyType>
【发布时间】:2010-06-01 15:15:55
【问题描述】:

与仅使用 List(Of MyType) 相比,使用 ConcurrentBag(Of MyType) 有什么优势? The MSDN page on the CB 声明

ConcurrentBag(Of T) 是线程安全的 包实现,针对 相同线程的场景 既生产数据又消费数据 放在包里

那么有什么优势呢?我可以理解 Concurrency 命名空间中其他集合类型的优势,但这一种让我感到困惑。

【问题讨论】:

    标签: c# .net vb.net multithreading


    【解决方案1】:

    在内部,ConcurrentBag 是使用几个不同的 List 实现的,每个写入线程一个。

    您引用的那句话的意思是,当从包中读取时,它将优先考虑为该线程创建的列表。这意味着,它会先检查该线程的列表,然后再冒着争用另一个线程列表的风险。

    这样可以在多个线程同时读取和写入时最大限度地减少锁争用。当读取线程没有列表,或者它的列表为空时,它必须锁定分配给不同线程的列表。但是,如果您有多个线程都读取和写入它们自己的列表,那么您将永远不会发生锁争用。

    【讨论】:

    【解决方案2】:

    这里最大的优势是ConcurrentBag&lt;T&gt; 可以安全地从多个线程访问,而LisT&lt;T&gt; 则不是。如果线程安全访问对您的方案很重要,那么像 ConcurrentBag&lt;T&gt; 这样的类型可能比 List&lt;T&gt; + 手动锁定对您更有优势。在我们真正回答这个问题之前,我们需要更多地了解您的情况。

    另外List&lt;T&gt; 是一个有序集合,而ConcurrentBag&lt;T&gt; 不是。

    【讨论】:

      【解决方案3】:

      TLDR;我会说本地锁定更快,但差异可以忽略不计(或者我翘起设置我的测试)。

      性能分析:

      private static IEnumerable<string> UseConcurrentBag(int count)
          {
              Func<string> getString = () => "42";
      
              var list = new ConcurrentBag<string>();
              Parallel.For(0, count, o => list.Add(getString()));
              return list;
          }
      
          private static IEnumerable<string> UseLocalLock(int count)
          {
              Func<string> getString = () => "42";
              var resultCollection = new List<string>();
              object localLockObject = new object();
              Parallel.For(0, count, () => new List<string>(), (word, state, localList) =>
              {
                  localList.Add(getString());
                  return localList;
              },
                  (finalResult) => { lock (localLockObject) resultCollection.AddRange(finalResult); }
                  );
      
              return resultCollection;
          }
      
          private static void Test()
          {
              var s = string.Empty;
              var start1 = DateTime.Now;
              var list = UseConcurrentBag(5000000);
              if (list != null)
              {
                  var end1 = DateTime.Now;
                  s += " 1: " + end1.Subtract(start1);
              }
      
              var start2 = DateTime.Now;
              var list1 = UseLocalLock(5000000);
              if (list1 != null)
              {
                  var end2 = DateTime.Now;
                  s += " 2: " + end2.Subtract(start2);
              }
      
              if (!s.Contains("sdfsd"))
              {
              }
          }
      

      使用 ConcurrentBag 对自身运行 3 次并有 500 万条记录的误差范围

      " 1: 00:00:00.4550455 2: 00:00:00.4090409"
      " 1: 00:00:00.4190419 2: 00:00:00.4730473"
      " 1: 00:00:00.4780478 2: 00:00:00.3870387"

      3 运行 ConcurrentBag 与 5M 记录的本地锁:

      " 1: 00:00:00.5070507 2: 00:00:00.3660366"
      " 1: 00:00:00.4470447 2: 00:00:00.2470247"
      " 1: 00:00:00.4420442 2: 00:00:00.2430243"

      有 5000 万条记录

      " 1: 00:00:04.7354735 2: 00:00:04.7554755"
      " 1: 00:00:04.2094209 2: 00:00:03.2413241"

      我会说本地锁稍微快一点

      更新:开启(至强 X5650 @ 2.67GHz 64 位 Win7 6 核)“本地锁定”似乎表现更好

      有 5000 万条记录。

      1:00:00:09.7739773 2:00:00:06.8076807
      1:00:00:08.8858885 2:00:00:04.6184618
      1:00:00:12.5532552 2:00:00:06.4866486

      【讨论】:

      • 我很想看看它是如何扩展的。随着核心数量的增加,锁竞争会增加时间。 ConcurrentBag 将为每个线程使用一个 List,因此不会增加争用(虽然更多的内存,旧的权衡)。
      • @MattC 嗨,马特,我在 E3-1225 v3 @ 3.20Ghz 16GB 64 位 Win7 上运行了这个。它是 4 核 CPU 如果您能找到具有不同内核数量的不同机器,请运行上面的代码,我很乐意将结果合并到答案中。谢谢。
      • 50M 记录。 1: 00:00:09.7739773 2: 00:00:06.8076807 1: 00:00:08.8858885 2: 00:00:04.6184618 1: 00:00:12.5532552 2: 00:00:06.4866486 至强 X56 7 GHz 64 位 7 core 在我的机器上变化很大!
      • @MattC 比率足够接近以确认“本地锁定”更快(9.7739773/6.8076807 = 1.43572792713, 8.8858885/4.6184618 = 1.92399307059, 12.5532552/6.4846148)
      • 是的,我同意如果你所做的只是添加到列表中,我会这样做。
      【解决方案4】:

      与其他并发集合不同,ConcurrentBag&lt;T&gt; 针对单线程使用进行了优化。
      List&lt;T&gt; 不同,ConcurrentBag&lt;T&gt; 可以同时在多个线程中使用。

      【讨论】:

        【解决方案5】:

        我认为您应该将其理解为“多个线程访问容器并且每个线程都可能产生和/或消耗数据”,它绝对是用于并行场景的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-07-27
          • 2012-11-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多