查看.NET源代码中HashSet<T>
类中的Contains
方法,我没有找到任何原因说明为什么Contains
不是线程安全的。
我预先加载了一个HashSet<T>
,并在多线程的AsParallel()
循环中检查Contains
。
这样做是否安全?我不想使用ConcurrentDictionary
,因为实际上我并不需要存储值。
查看.NET源代码中HashSet<T>
类中的Contains
方法,我没有找到任何原因说明为什么Contains
不是线程安全的。
我预先加载了一个HashSet<T>
,并在多线程的AsParallel()
循环中检查Contains
。
这样做是否安全?我不想使用ConcurrentDictionary
,因为实际上我并不需要存储值。
public class MyClass
{
private long value2;
public int Value1 { get; set; }
// Value2 is lazily loaded in a very primitive
// way (note that Lazy<T> *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<T> 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<T>
。这个不可变集合声称自己是线程安全的,因此我们不必担心HashSet<T>
的非官方线程安全。
var builder = ImmutableHashSet.CreateBuilder<string>(); // The builder is not thread safe
builder.Add("value1");
builder.Add("value2");
ImmutableHashSet<string> set = builder.ToImmutable();
...
if (set.Contains("value1")) // Thread safe operation
{
...
}
.NET Framework 4引入了System.Collections.Concurrent命名空间,其中包括多个既线程安全又可扩展的集合类。多个线程可以在不需要额外同步用户代码的情况下安全有效地向这些集合中添加或删除项。当您编写新代码时,请在多个线程同时写入集合时使用并发集合类。如果您只是从共享集合中读取数据,则可以使用System.Collections.Generic命名空间中的类。我们建议您不要使用1.0集合类,除非您需要针对.NET Framework 1.1或更早的运行时。
由于Contains
不修改集合,它仅是一个读取操作,并且由于HashSet
在System.Collections.Generic
中,因此同时调用Contains
是绝对没问题的。