极端线程安全集合
Extreme Thread Safe Collection
我在 .Net 4.5 中有一个 ConcurrentBag,我从数据库中存储了大约 4,000 行。我正在存储 DTO。
我的整个应用程序都依赖于此。我有 return 整个列表的功能,也有 return 单个项目的功能。我的代码中很多地方都在对集合等进行 LINQ 查询
我将其全部推向了生产环境,在网站上获得了可观的流量,并且立即达到了 100% cpu。我用了iis诊断工具,果然有50+线程死锁,等待ConcurrentBag。
文档说这个集合是线程安全的,但要么这不是真的,要么这个集合的性能不好从而间接地使其不是线程安全的。
很遗憾,此合集不是只读的。如果通过 ID return 查找的函数之一为空,它将访问 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);
msdn 状态:ConcurrentBag 的所有 public 和受保护成员都是线程安全的,可以从多个线程并发使用。但是,通过 ConcurrentBag 实现的接口之一访问的成员,包括扩展方法,不保证是线程安全的,可能需要由调用者同步。
简单的答案是您使用了错误的容器。
ConcurrentBag
不是通用的。它更像是一个可重用对象池,您可以(通常作为最后一步)将其缩减为单个非并发值。它可以用来解决的一个这样的问题是同时总结一个列表。
如果您对 ConcurrentBag
的主要用法偏离了 add/remove,并且您经常枚举该集合,那么您就使用错了。
如果您 post 更多代码,您将获得更有针对性的帮助。并发性是理解问题对于提供高性能解决方案非常重要的领域之一。
编辑:
ConcurrentDictionary
将适用于您正在做的事情。诀窍是您不想使用 ConcurrentDictionary.Values
——这将锁定字典并复制其内容。如果你只是使用它的 IEnumerable<T>
接口,你会没事的。例如:
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;
}
我在 .Net 4.5 中有一个 ConcurrentBag,我从数据库中存储了大约 4,000 行。我正在存储 DTO。
我的整个应用程序都依赖于此。我有 return 整个列表的功能,也有 return 单个项目的功能。我的代码中很多地方都在对集合等进行 LINQ 查询
我将其全部推向了生产环境,在网站上获得了可观的流量,并且立即达到了 100% cpu。我用了iis诊断工具,果然有50+线程死锁,等待ConcurrentBag。
文档说这个集合是线程安全的,但要么这不是真的,要么这个集合的性能不好从而间接地使其不是线程安全的。
很遗憾,此合集不是只读的。如果通过 ID return 查找的函数之一为空,它将访问 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);
msdn 状态:ConcurrentBag 的所有 public 和受保护成员都是线程安全的,可以从多个线程并发使用。但是,通过 ConcurrentBag 实现的接口之一访问的成员,包括扩展方法,不保证是线程安全的,可能需要由调用者同步。
简单的答案是您使用了错误的容器。
ConcurrentBag
不是通用的。它更像是一个可重用对象池,您可以(通常作为最后一步)将其缩减为单个非并发值。它可以用来解决的一个这样的问题是同时总结一个列表。
如果您对 ConcurrentBag
的主要用法偏离了 add/remove,并且您经常枚举该集合,那么您就使用错了。
如果您 post 更多代码,您将获得更有针对性的帮助。并发性是理解问题对于提供高性能解决方案非常重要的领域之一。
编辑:
ConcurrentDictionary
将适用于您正在做的事情。诀窍是您不想使用 ConcurrentDictionary.Values
——这将锁定字典并复制其内容。如果你只是使用它的 IEnumerable<T>
接口,你会没事的。例如:
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;
}