c# - 在 HashSet<T> 中是否包含线程安全

标签 c# multithreading hashset

查看 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-WaitedHashSet<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/

相关文章:

Java:为什么我的 HashSet 和 TreeSet 包含重复项?

c# - 编程和单元测试最佳实践

c# - javascript函数中的查询字符串

c# - 面向对象范式 - C#

java - 对于下面的程序,当线程完成工作时,为什么主线程不停止?

java - 从 WAR 自动运行线程

linux - pthread_mutex_unlock 立即上下文切换吗?

java - HashSet 中的 HashCode 行为

c# - 从 CodeCompileUnit 编译可收集的程序集

java - 为什么我的哈希集如此耗费内存?