views:

68

answers:

3

I have a lookup table (LUT) of thousands integers that I use on a fair amount of requests to compute stuff based on what was fetched from database.

If I simply create a standard singleton to hold the LUT, is it automatically persisted between requests or do I specifically need to push it to the Application state?

If they are automatically persisted, then what is the difference storing them with the Application state?

How would a correct singleton implementation look like? It doesn't need to be lazily initialized, but it needs to be thread-safe (thousands of theoretical users per server instance) and have good performance.

EDIT: Jon Skeet's 4th version looks promising http://csharpindepth.com/Articles/General/Singleton.aspx

public sealed class Singleton
{
    static readonly Singleton instance=new Singleton();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Singleton()
    {
    }

    Singleton()
    {
    }

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

    // randomguy's specific stuff. Does this look good to you?
    private int[] lut = new int[5000];

    public int Compute(Product p) {
        return lut[p.Goo];
    }
}
+2  A: 

I wouldn't rely on a static being persisted between requests. [There is always the, albeit unlikely, chance that the process would be reset between requests.] I'd recommend HttpContext's Cache object for persisting shared resources between requests.

joe.liedtke
HttpContext's Cache only lasts while the application domain is active too, so it's no better in that regard. Indeed, the cache is designed with expiry rather than permanence in mind, so it really doesn't serve the same purpose (though there are overlapping cases where either have their value).
Jon Hanna
+1  A: 

Yes, static members persists (not the same thing as persisted - it's not "saved", it never goes away), which would include implementations of a singleton. You get a degree of lazy initialisation for free, as if it's created in a static assignment or static constructor, it won't be called until the relevant class is first used. That creation locks by default, but all other uses would have to be threadsafe as you say. Given the degree of concurrency involved, then unless the singleton was going to be immutable (your look-up table doesn't change for application lifetime) you would have to be very careful as to how you update it (one way is a fake singleton - on update you create a new object and then lock around assigning it to replace the current value; not strictly a singleton though it looks like one "from the outside").

The big danger is that anything introducing global state is suspect, and especially when dealing with a stateless protocol like the web. It can be used well though, especially as an in-memory cache of permanent or near-permanent data, particularly if it involves an object graph that cannot be easily obtained quickly from a database.

The pitfalls are considerable though, so be careful. In particular, the risk of locking issues cannot be understated.

Edit, to match the edit in the question:

My big concern would be how the array gets initialised. Clearly this example is incomplete as it'll only ever have 0 for each item. If it gets set at initialisation and is the read-only, then fine. If it's mutable, then be very, very careful about your threading.

Also be aware of the negative effect of too many such look-ups on scaling. While you save for mosts requests in having pre-calculation, the effect is to have a period of very heavy work when the singleton is updated. A long-ish start-up will likely be tolerable (as it won't be very often), but arbitrary slow downs happening afterwards can be tricky to trace to their source.

Jon Hanna
A: 

Edit: See Jon's comments about read-only locking.

It's been a while since I've dealt with singleton's (I prefer letting my IOC container deal with lifetimes), but here's how you can handle the thread-safety issues. You'll need to lock around anything that mutates the state of the singleton. Read only operations, like your Compute(int) won't need locking.

// I typically create one lock per collection, but you really need one per set of atomic operations; if you ever modify two collections together, use one lock.
private object lutLock = new object();
private int[] lut = new int[5000];

public int Compute(Product p) {
    return lut[p.Goo];
}

public void SetValue(int index, int value)
{
    //lock as little code as possible. since this step is read only we don't lock it.
    if(index < 0 || index > lut.Length)
    {
        throw new ArgumentException("Index not in range", "index");
    }
    // going to mutate state so we need a lock now
    lock(lutLock)
    {
        lut[index] = value;
    }
}
Ryan
Not necessarily. In this case it's unlikely to be an issue, but if Compute needed more than one value from lut, then it could easily end up using a mix of values from before and after a change. That could be fine or disasterous depending on the application. A ReaderWriterLockSlim might be appropriate here. Also, if SetValue was actually an Update method that set many values, it may be better to create a new one, and then lock around assigning it to lut.
Jon Hanna
@Ryan, Jon: Why don't read-only operations need locking? Couldn't multiple requests read the same memory at the same time causing problems?
randomguy
@randomguy, read-only operations *do* need locking, if there are also write-operations, which is why I disagreed with Ryan here, though sometimes you can get by on single-value reads of references or built-ins smaller than machine-size, if being stale due to the lack of a memory barrier isn't an issue (threads cache memory, so if thread B reads after thread A wrote, it might get the previous value, which can be crucial or insignif depending on purpose). If the only operation after set-up is reading, then that doesn't need locking. Reads never hurt reads but get hurt by writes.
Jon Hanna
That said, if your only risk was of memory-cache staleness, then you could use volatile instead of locking on an object of that size. If however a read operation would read more than one atomic value at a time, volatile isn't good enough, and you need to lock. (You can sometimes also treat writes that will only ever add, never change, in a more liberal manner, but knowing whether you can or not gets complicated fast - to the point where locking may be a good idea even if you're 99.99% sure you don't need to).
Jon Hanna
@Jon Thanks for raising the point about getting mixed before/after values. I hadn't considered that before because my use cases are always reading and writing one record at a time. I'll keep this in mind.
Ryan