Shaangor
Shaangor

Reputation: 31

Proper locking in thread-safe self-generating list (C#)

I have a singleton IEnumerable that generates a sequence of numbers. The sequence is interable (basically indefinitely) and I generate the next number in the sequence only when needed.

public class Generator:IEnumerable<long> {

    private Generator() { }

    private static volatile Generator instance=new Generator();
    private static readonly object syncRoot=new object();
    public static Generator Instance { get { return instance; } }

    private static List<long> numsList=new List<long>();

    private void GenerateNextNumber() {
        long number;
        //Code to generate next number
        numsList.Add(number);
    }

    private long GenerateToNthNumber(int n) {
        lock(syncRoot) {
            while(numsList.Count<n)
                GenerateNextNumber();
        }
        return numsList[n-1];
    }

    public static long GetNthNumber(int n) {
        return Instance.GenerateToNthNumber(n);
    }

    private class GeneratorEnumerator:IEnumerator<long> {
        private int index=0;

        public long Current { get { return GetNthNumber(index); } }

        public void Dispose() { }

        object System.Collections.IEnumerator.Current { get { return GetNthNumber(index); } }

        public bool MoveNext() {
            index++;
            return true;
        }

        public void Reset() {
            index=0;
        }
    }

    public IEnumerator<long> GetEnumerator() {
        return new GeneratorEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
}

This code works enumerating through and summing the numbers in concurrent threads. Is there a way to prevent having to lock every time GenerateToNthNumber is called? I tried this code:

    private long GenerateToNthNumber(int n) {
        if(numsList.Count<n) {
            lock(syncRoot) {
                while(numsList.Count<n)
                    GenerateNextNumber();
            }
        }
        return numsList[n-1];
    }

But when testing enumerating through and summing the numbers in multiple concurrent threads, not all the results end up with the same sum. My objective is to have non-blocking reads if the number being asked for is already generated, if that is even possible. Is there a better way to do this?

Upvotes: 3

Views: 316

Answers (2)

supercat
supercat

Reputation: 81159

The way List is implemented, it cannot be safely read in one thread while it is being written in another. I would suggest that instead you use nested known-size arrays which, once allocated, are never abandoned (e.g. once an array is allocated that will hold theList[15691], the item will never be held by any other array). Such things may easily be used to implement an add-only list which requires locking when adding items, but is inherently thread-safe for reading without locking.

Upvotes: 1

Patrick Huber
Patrick Huber

Reputation: 756

Have you thought about using a thread safe collection?

http://msdn.microsoft.com/en-us/library/dd997305.aspx

Upvotes: 0

Related Questions