在HashSet <t> </t>中包含线程安全

时间:2015-03-10 09:52:38

标签: c# multithreading hashset

查看.NET源代码中Contains类中HashSet<T>的代码,我找不到Contains不是线程安全的原因吗?

我正在提前加载HashSet<T>值,然后在多线程。Contains循环中检查AsParallel()

有什么理由说这不安全。 当我实际上不需要存储值时,我不愿使用ConcurrentDictionary

3 个答案:

答案 0 :(得分:12)

通常通常)仅用于读取的集合是“非正式”线程安全的(我知道.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()

使用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<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;
    }
}

答案 1 :(得分:1)

从Microsoft:Thread-Safe Collections

  

.NET Framework 4引入了System.Collections.Concurrent命名空间,该命名空间包括几个线程安全和可伸缩的集合类。多个线程可以安全有效地从这些集合中添加或删除项目,而无需用户代码中的额外同步。在编写新代码时,只要有多个线程将并发写入集合,就使用并发集合类。 如果您仅从共享集合中读取内容,则可以使用System.Collections.Generic命名空间中的类。我们建议您不要使用1.0集合类,除非您需要定位。 NET Framework 1.1或更早版本的运行时。

由于Contains不会修改集合,因此它只是一个读取操作,并且由于HashSet位于System.Collections.Generic中,因此同时调用Contains绝对可以。

答案 2 :(得分:0)

鉴于您要提前加载值,可以使用ImmutableHashSet<T>库中的System.Collections.Immutableimmutable collections将自己宣传为thread safe,因此我们不必担心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
{
 ...
}