【问题标题】:Extreme Thread Safe Collection极端线程安全集合
【发布时间】:2015-08-15 22:19:09
【问题描述】:

我在 .Net 4.5 中有一个 ConcurrentBag,我从数据库中存储了大约 4,000 行。我正在存储 DTO。

我的整个应用程序都依赖于此。我有返回整个列表的函数,也有返回单个项目的函数。我的代码中有很多地方我正在对集合等进行 LINQ 查询。

我将其全部投入生产,在获得大量流量的站点上,并立即 100% cpu。我用了iis诊断工具,果然有50+线程死锁,在ConcurrentBag上等待。

文档说这个集合是线程安全的,但要么这不是真的,要么这个集合的性能不好,因此间接导致它不是线程安全的。

不幸的是,这个集合不是只读的。如果通过 ID 查找的函数之一返回 null,它将访问 Web 服务,并添加它。

我也将它转换为 ConcurrentDictionary,并且遇到了同样的问题。在 .Values 属性上锁定天数。

在最极端的情况下,最快和最线程安全的解决方案是什么?

private ConcurrentBag<Students> _students;
public static ConcurrentBag<DestinyHash> GetStudents()
{
   if (_students == null) { _students = new ConcurrentBag<Students>(); }

   return _students;
}

public static Student GetStudentByID(int id) 
{
   if (GetStudents().Any(x => x.id == id)) { return ... }

   _students.Add(getStudentFromDb(id));
   return...
}

使用示例 - 遍布整个应用程序。

Helper.GetStudents().FirstOrDefault(x => x.name == "foo" && x.status == "bar");
Helper.GetStudentByID(50);

【问题讨论】:

  • 如果您需要能够检索特定项目,ConcurrentBag 绝对不是正确的数据结构。你是如何使用它的?我们需要更多详细信息才能为您提供帮助。
  • @ThomasLevesque 我基本上把它当作一个列表。在 ConcurrentBag 中执行 LINQ,例如 Where、Count、FirstOrDefault。这就是我需要的,一个内存列表,可以为应用程序的其余部分提供服务。
  • 我的问题是你为什么使用 50+ 线程?我无法想象这么多设备的速度会有所提高——实际上恰恰相反。
  • @Enigmativity 我没有偏离默认值。这正是调试诊断所说的。
  • @bladefist - 我看不出你的回答是如何回答我的问题的。

标签: c# multithreading linq concurrency concurrentdictionary


【解决方案1】:

简单的答案是您使用了错误的容器。

ConcurrentBag 不是通用的。它旨在更像是一个可重用对象池,您可能(通常作为最后一步)将其减少为单个非并发值。它可以用来解决一个这样的问题是同时总结一个列表。

如果您对 ConcurrentBag 的主要用法偏离了添加/删除,并且您经常枚举集合,那么您使用它是错误的。

如果您发布更多代码,您将获得更有针对性的帮助。并发是理解问题对于提供高性能解决方案非常重要的领域之一。

编辑:

ConcurrentDictionary 将适用于您正在做的事情。诀窍是您不想使用ConcurrentDictionary.Values——这将锁定字典并复制其内容。如果你只使用它的IEnumerable&lt;T&gt; 接口,你会没事的。例如:

private ConcurrentDictionary<int,Student> _students;

public static IEnumerable<Student> GetStudents()
{
   return _students.Select(x => x.Value);
}

public static Student GetStudentByID(int id) 
{
   Student s;
   if(_students.TryGetValue(id, out s)) return s;

   s = getStudentFromDb(id);
   _students[id] = s;

   return s;
}

【讨论】:

  • 那我应该用什么?我有一个静态 ConcurrentBag 和一个函数 GetList() 来获取它。如果它为空,我从数据库中填充它,然后返回 ConcurrentBag。然后在整个代码中,它对 ConcurrentBag 进行 linq 查询。似乎每个人都同意我使用了错误的数据结构。我不知道什么是正确的。我需要一个列表,在内存中,数百万个线程可以同时枚举。偶尔会添加一个项目。我不在乎其他线程是否立即获得该项目。
  • 您能解释一下为什么使用 Select off a ConcurrentDictionary 可以,但不能使用 ConcurrentBag?我知道使用 Values 不好,谢谢。但我觉得 Select 应该没问题。
  • ConcurrentBag 并不是要经常列举。它与ConcurrentDictionary.Values 有相同的性能问题——它锁定并复制整个集合。
【解决方案2】:

msdn 声明:ConcurrentBag 的所有公共和受保护成员都是线程安全的,并且可以从多个线程同时使用。但是,通过 ConcurrentBag 实现的接口之一访问的成员(包括扩展方法)保证是线程安全的,可能需要由调用者同步。

【讨论】:

  • 这是有道理的。这绝对是问题所在。我应该使用什么? SQL 有一个“with nolock”命令,您的读取不会将其他线程锁定在读取之外。这就是我在 C# 中需要的。我很好,其他线程未提交。
  • 你应该检查一个支持延迟加载的 ORM 框架。该示例可以添加重复键。由于重复,集合可能是无限的,不会出现死锁。
  • 顺便说一句,如果您运行那么多线程,您可能想要分析您的应用程序。它可能会花费大量时间在它们之间切换而杀死您的进程。 (用增量负载对其进行测试,直到它崩溃……测试人员称之为压力测试。)
猜你喜欢
  • 2013-12-10
  • 1970-01-01
  • 2011-02-28
  • 2011-12-15
  • 2013-07-06
  • 1970-01-01
  • 1970-01-01
  • 2011-05-29
  • 2012-05-27
相关资源
最近更新 更多