【问题标题】:C# ConcurrentBag memory consumption when iteratingC# ConcurrentBag 迭代时的内存消耗
【发布时间】:2015-10-18 04:43:02
【问题描述】:

如果我先发布代码会更容易:

static void Main(string[] args)
{
    List<double> testLst = new List<double>();
    for (int i = 0; i < 20000000; i++) { testLst.Add(i); }

我已经填充了一个包含 20,000,000 个元素的列表。我在任务管理器中看到该进程正在使用 ~300MB。如果我使用 foreach 循环遍历列表:

    foreach (var a in testLst.Take(10)) 
    {
        Console.WriteLine(a);
    }
}

内存使用量没有增加(我在 Console.WriteLine 上设置了一个断点,正如我所说,我正在使用任务管理器对其进行测量)。现在,如果我用 ConcurrentBag 替换 List:

static void Main(string[] args)
{
    ConcurrentBag<double> testCB = new ConcurrentBag<double>();
    for (int i = 0; i < 20000000; i++) { testCB.Add(i); }

    foreach (var a in testCB.Take(10)) 
    {
        Console.WriteLine(a);
    }
}

foreach-loop 之前的内存使用量为 450~500MB。问题是:为什么如果在 foreach 循环内部使用量跃升至 ~900MB?

我希望 ConcurrentBag 与 List 相比消耗更多内存,但我不明白为什么要使用这么多内存进行迭代。

(我在类似但不同的场景中使用 ConcurrentBag,我知道在这种情况下使用它没有意义)

【问题讨论】:

  • ConcurrentBag 在创建迭代器时需要拍摄某种快照——因此更高的内存使用率似乎是合理的。检查参考源代码以查看详细信息...
  • 长话短说:从不迭代线程安全的集合。它会吞噬内存并且您获得的值通常是陈旧的。如果您需要可预测的结果,那么 lock 关键字是无可替代的。

标签: c# list memory foreach


【解决方案1】:

来自ConcurrentBag.GetEnumerator docs(强调我的):

枚举表示包中内容的即时快照。在调用 GetEnumerator 后,它不会反映对集合的任何更新。枚举器可以安全地与对包的读取和写入同时使用。

查看source,您可以看到它创建了包的副本:

public IEnumerator<T> GetEnumerator()
{
    // Short path if the bag is empty
    if (m_headList == null)
        return new List<T>().GetEnumerator(); // empty list

    bool lockTaken = false;
    try
    {
        FreezeBag(ref lockTaken);
        return ToList().GetEnumerator();
    }
    finally
    {
        UnfreezeBag(lockTaken);
    }
}

顾名思义,ToList() 返回一个List&lt;T&gt;(它不是扩展方法,它是一个私有成员函数)。

作为旁注,return new List&lt;T&gt;().GetEnumerator(); 行并不漂亮...可以改为 return Enumerable.Empty&lt;T&gt;().GetEnumerator();

【讨论】:

  • 感谢您的快速回复! :)
猜你喜欢
  • 2012-12-28
  • 1970-01-01
  • 2012-03-15
  • 1970-01-01
  • 2016-08-30
  • 1970-01-01
  • 2019-05-05
  • 1970-01-01
  • 2010-10-12
相关资源
最近更新 更多