C# 极端线程安全集合

C# 极端线程安全集合,c#,multithreading,linq,concurrency,concurrentdictionary,C#,Multithreading,Linq,Concurrency,Concurrentdictionary,我在.NET4.5中有一个ConcurrentBag,我从数据库中存储了大约4000行。我正在存储DTO 我的整个应用程序都依赖于此。我有返回整个列表的函数,也有返回单个项的函数。在我的代码中有很多地方我都在对集合进行LINQ查询,等等 我把它全部推到了生产现场,在现场获得了可观的流量,并立即100%的cpu。我使用了iis诊断工具,果然有50多个线程处于死锁状态,等待ConcurrentBag 文档中说此集合是线程安全的,但不是这样,就是此集合的性能不好,从而间接导致它不是线程安全的 不幸的是

我在.NET4.5中有一个ConcurrentBag,我从数据库中存储了大约4000行。我正在存储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...
}

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

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

ConcurrentBag不是通用的。它的用途更像是一个可重用对象池,作为最后一步,您通常可以将其缩减为单个非并发值。它可以用来解决的一个问题是同时汇总一个列表

如果您对ConcurrentBag的主要使用偏离了add/remove,并且您经常枚举集合,那么您使用它是错误的

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

编辑:

ConcurrentDictionary适用于您所做的工作。诀窍是你不想使用ConcurrentDictionary.Values——这将锁定字典并复制其内容。如果您只使用它的IEnumerable接口,就可以了。例如:

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绝对不是正确的数据结构。你到底是怎么用的?我们需要更多的细节来帮助您。@thomasleveque我基本上把它当作一个列表。立即从ConcurrentBag中执行LINQ,如Where、Count、FirstOrDefault。这就是我需要的,内存中的一个列表,可以为应用程序的其余部分提供服务。我的问题是,你为什么要使用50多个线程?我无法想象你能在这么多人的帮助下获得任何速度提升,事实恰恰相反。@Enigmativity我没有偏离默认设置。这正是调试诊断所说的。@bladefist-我不知道您的回答如何回答我的问题。那么我应该使用什么呢?我有一个静态ConcurrentBag和一个函数GetList来获取它。如果为null,则从DB填充它,然后返回ConcurrentBag。然后在整个代码中,它在ConcurrentBag上执行linq查询。似乎每个人都同意我使用了错误的数据结构。我不知道什么是正确的。我需要一个列表,在内存中,数百万个线程可以同时枚举。偶尔会添加一个项目。我不在乎其他线程是否立即获得该项。您能解释一下为什么使用Select off a ConcurrentDictionary可以,而不是ConcurrentBag吗?我明白使用值是不好的,谢谢。但是我觉得Select应该可以。ConcurrentBag不应该经常被枚举。它与ConcurrentDictionary.Values有相同的性能问题-它锁定并复制整个集合。这很有意义。这绝对是问题所在。我应该用什么?SQL有一个nolock命令,在该命令中,您的读取不会将其他线程锁定在读取之外。这就是我在C语言中需要的。我很好,其他线程都在读未提交。你应该检查一个支持延迟加载的ORM框架。该示例可以添加重复的密钥。不会出现死锁。由于存在重复项,集合可能是无限的。顺便说一句,如果您运行了那么多线程,您可能希望分析应用程序。它可能会花费大量时间在它们之间切换,从而扼杀您的流程。用增量负载测试它,直到它断裂。。。测试人员称之为压力测试。
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;
}