简体   繁体   中英

Lock needed on singleton lookup table?

I have a singleton below. I have multiple threads using the lookup to check if values are valid. It's been awhile since I've done anything with shared memory, so I want to make sure what locks are necessary. I'm unsure if I need a concurrent set instead of HashSet since I'm only inserting values once.

I have [MethodImpl(MethodImplOptions.Synchronized)] on the Instance property because I read that properties aren't sycrhonized (makes sense). This should prevent multiple instances being created, although I'm not sure if I should really worry about that (just extra cost of reloading the set?).

Should I make the FipsIsValid function Syncrhonized, or use some sort of concurrent set? Or are neither necessary?

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);
    }
}

Should I make the FipsIsValid function Syncrhonized, or use some sort of concurrent set? Or are neither necessary?

I think the key to this answer is the fact that you are only performing lookups on the HashSet , and not mutating it. Since it is initialized once, and only once, there is no need to synchronize the lookup.

If you do decide that along the way that you do need to mutate it, then using a proper lock or a concurrent collection would be needed.

On a side note, you can simplify your singleton by initializing the instance field once inside a static constructor:

private static FipsLookup instance;
static FipsLookup() 
{
    instance = new FipsLookup();
}

And now you can make Instance return the field, with no need to use [MethodImpl(MethodImplOptions.Synchronized)] :

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

This is safe because Instance is synchronized which is equivalent to a lock. All writes happen under that lock. Releasing the lock flushes all writes (a release barrier).

Also, all read first go through the lock. It is not possible to observe a partially written hashset. A previous version of this answer made the following incorrect claims:

This is not strictly safe (under ECMA) because readers might see a half-written HashSet . In practice it is safe (on the Microsoft CLR because all stores are releases) but I wouldn't use it because there is no reason to.

When writing this I did not notice the MethodImplOptions.Synchronized . So for your entertainment this is what happens when you forget a lock.

Probably, you should be using Lazy<T> which handles this for you and it gives you lock-free reads.

MethodImplOptions.Synchronized on static members is a little evil because it locks on the type object of the class. Let's hope nobody else is locking on this (shared) object. I would fail this in a code review, mostly because there is no reason to introduce this code smell.

HashSet class is not thread safe and there is no garantee that you can access it from multiple threads and all will be ok. I'd prefer to use ConcurrentDictionary instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM