在HashSet 中包含线程安全
查看.NET源代码中HashSet
类中Contains
的代码,我找不到Contains
不是线程安全的原因吗?
我正在提前使用值加载HashSet
,然后在multithreading中检查Contains
。 AsParallel()
循环。
这有什么理由不安全吗? 当我实际上不需要存储值时,我不愿意使用ConcurrentDictionary
。
通常 ( 通常 )仅用于读取的集合是“非正式”线程安全的(.NET中没有我知道在读取期间自行修改的集合)。 有一些警告:
- 项目本身不能是线程安全的(但是使用
HashSet
这个问题应该被最小化,因为你无法从中提取项目。仍然GetHashCode()
和Equals()
必须是线程安全的。如果,例如,它们访问按需加载的延迟对象,它们可能不是线程安全的,或者它们可能缓存/记忆某些数据以加速后续操作) - 您必须确保在最后一次写入之后有一个
Thread.MemoryBarrier()
(在与写入相同的线程中完成)或等效,否则另一个线程上的读取可能会读取不完整的数据 - 您必须确保在每个线程中(与您执行写操作的线程不同),在执行第一次读取之前,有一个
Thread.MemoryBarrier()
。 请注意,如果HashSet
在创建/启动其他线程之前已“准备好”(最后使用Thread.MemoryBarrier()),则Thread.MemoryBarrier()
不是必需的,因为线程不能有一个陈旧的内存读取(因为它们不存在)。 各种操作会导致隐式Thread.MemoryBarrier()
。 例如,如果在HashSet
之前创建的线程被填充,则输入Wait()
并在填充HashSet
之后un-Waited
(加上其Thread.MemoryBarrier()
),退出Wait()
会导致一个隐式的Thread.MemoryBarrier()
使用memoization / lazy loading /任何你想要调用它的类的简单示例可以打破线程的安全性。
public class MyClass { private long value2; public int Value1 { get; set; } // Value2 is lazily loaded in a very primitive // way (note that Lazy *can* be used thread-safely!) public long Value2 { get { if (value2 == 0) { // value2 is a long. If the .NET is running at 32 bits, // the assignment of a long (64 bits) isn't atomic :) value2 = LoadFromServer(); // If thread1 checks and see value2 == 0 and loads it, // and then begin writing value2 = (value), but after // writing the first 32 bits of value2 we have that // thread2 reads value2, then thread2 will read an // "incomplete" data. If this "incomplete" data is == 0 // then a second LoadFromServer() will be done. If the // operation was repeatable then there won't be any // problem (other than time wasted). But if the // operation isn't repeatable, or if the incomplete // data that is read is != 0, then there will be a // problem (for example an exception if the operation // wasn't repeatable, or different data if the operation // wasn't deterministic, or incomplete data if the read // was != 0) } return value2; } } private long LoadFromServer() { // This is a slow operation that justifies a lazy property return 1; } public override int GetHashCode() { // The GetHashCode doesn't use Value2, because it // wants to be fast return Value1; } public override bool Equals(object obj) { MyClass obj2 = obj as MyClass; if (obj2 == null) { return false; } // The equality operator uses Value2, because it // wants to be correct. // Note that probably the HashSet doesn't need to // use the Equals method on Add, if there are no // other objects with the same GetHashCode // (and surely, if the HashSet is empty and you Add a // single object, that object won't be compared with // anything, because there isn't anything to compare // it with! :-) ) // Clearly the Equals is used by the Contains method // of the HashSet return Value1 == obj2.Value1 && Value2 == obj2.Value2; } }
假设您提前使用值加载了集合,则可以使用System.Collections.Immutable
库中的ImmutableHashSet
。 不可变集合将自己宣传为线程安全 ,因此我们不必担心HashSet
的“非官方”线程安全性。
var builder = ImmutableHashSet.CreateBuilder(); // The builder is not thread safe builder.Add("value1"); builder.Add("value2"); ImmutableHashSet set = builder.ToImmutable();
…
if (set.Contains("value1")) // Thread safe operation { ... }