查看 Contains
的代码在HashSet<T>
.NET 源代码中的类,我找不到任何原因 Contains
不是线程安全的吗?
我正在加载 HashSet<T>
提前使用值,然后检查 Contains
在多线程中。 AsParallel()
循环。
这有什么不安全的原因吗?
我不愿意使用 ConcurrentDictionary
当我实际上不需要存储值时。
最佳答案
通常(通常)仅用于读取的集合是“非官方”线程安全的(我知道 .NET 中没有任何集合在读取期间会自行修改).有一些注意事项:
- 项目本身可能不是线程安全的(但对于
HashSet<T>
,这个问题应该被最小化,因为你不能从中提取项目。仍然GetHashCode()
和Equals()
必须是线程安全的。例如,如果它们访问按需加载的惰性对象,它们可能不是线程安全的,或者它们可能缓存/内存一些数据以加速后续操作) - 你必须确保在最后一次写入之后有一个
Thread.MemoryBarrier()
(在与写入相同的线程中完成)或等效,否则在另一个线程上读取可能会读取不完整的数据 - 您必须确保在每个线程(不同于您进行写入的线程)中,在进行第一次读取之前都有一个
Thread.MemoryBarrier()
。 .请注意,如果HashSet<T>
在创建/启动其他线程之前“准备好”(最后使用 Thread.MemoryBarrier()),然后是Thread.MemoryBarrier()
没有必要,因为线程不能读取过时的内存(因为它们不存在)。各种操作导致隐式Thread.MemoryBarrier()
.例如,如果在HashSet<T>
之前创建的线程被填满,输入一个Wait()
并且是un-Waited
在HashSet<T>
之后被填满(加上它的Thread.MemoryBarrier()
),退出Wait()
导致隐式Thread.MemoryBarrier()
一个使用记忆化/延迟加载/任何你想调用它的类的简单示例,并且以这种方式可以破坏线程安全。
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;
}
}
关于c# - 在 HashSet<T> 中是否包含线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28960534/