在单例查找表上需要锁

本文关键字:单例 查找 | 更新日期: 2023-09-27 17:53:34

下面有一个单例。我有多个线程使用查找来检查值是否有效。我已经有一段时间没有使用共享内存了,所以我想确定哪些锁是必需的。我不确定我是否需要一个并发集而不是HashSet,因为我只插入值一次。

我在Instance属性上有[MethodImpl(MethodImplOptions.Synchronized)],因为我读到属性不同步(有意义)。这可以防止创建多个实例,尽管我不确定我是否真的应该担心这一点(只是重新加载集合的额外成本?)。

我应该让FipsIsValid函数同步,还是使用某种并发集?还是两者都没有必要?

public class FipsLookup
{
    private static FipsLookup instance;
    private HashSet<string> fips;
    private FipsLookup()
    {
        using (HarMoneyDB db = new HarMoneyDB())
        {
            instance.fips = new HashSet<string>(db.Counties.Select(c => c.FIPS).ToArray());
        }
    }
    [MethodImpl(MethodImplOptions.Synchronized)]
    public static FipsLookup Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new FipsLookup();
            }
            return instance;
        }
    }
    public static bool FipsIsValid(string fips)
    {
        var instance = FipsLookup.Instance;
        return instance.fips.Contains(fips);
    }
}

在单例查找表上需要锁

我应该使FipsIsValid函数同步,还是使用某种方式并发集的?还是两者都没有必要?

我认为这个答案的关键是你只是在HashSet上执行查找,而不是改变它。因为它初始化一次,而且只初始化一次,所以不需要同步查找。

如果您确定需要改变它,那么就需要使用适当的lock或并发集合。

另外,您可以通过在静态构造函数中初始化实例字段来简化您的单例:
private static FipsLookup instance;
static FipsLookup() 
{
    instance = new FipsLookup();
}

现在你可以让Instance返回字段,不需要使用[MethodImpl(MethodImplOptions.Synchronized)]:

public static FipsLookup Instance
{
    get
    {
        return instance;
    }
}

这是安全的,因为Instance是同步的,相当于锁。所有写操作都在该锁下进行。释放锁将刷新所有写操作(释放障碍)。

同样,所有的读先通过锁。不可能观察到部分写入的哈希集。这个答案的前一个版本做出了以下错误的断言:

<引用类>

这不是严格安全的(在ECMA下),因为读者可能会看到一个写了一半的HashSet。在实践中,它是安全的(在Microsoft CLR上,因为所有的商店都是发行版),但我不会使用它,因为没有理由。

写这篇文章时,我没有注意到MethodImplOptions.Synchronized。这是你忘记锁的后果

也许,你应该使用Lazy<T>来为你处理这个问题,它给你无锁读取。

静态成员上的

MethodImplOptions.Synchronized有点邪恶,因为它锁定了类的类型对象。让我们希望没有其他人锁定这个(共享)对象。我会在代码审查中失败,主要是因为没有理由引入这种代码气味。

HashSet类不是线程安全的,并且不能保证您可以从多个线程访问它并且一切正常。我更喜欢使用ConcurrentDictionary