Monday, 20 March 2017

Threading 03 Locking

So we've played around a bit with threads, but we haven't really made them interact with each other. What we are going to build is a simple pick a straw simulation, we're going to create a repository of straws with long and short straws, then we are going to have an equal number of threads pull the straws and see what happens.

So first let's create an enum of long and short straws.

enum Straw { Long, Short }

We create our enum at our class level so that we can share it between all of our classes, generally I put all of my enums inside of their own namespace and just include the namespace in the using statements where needed.

using System;
using System.Linq;
using System.Threading;

namespace pc.ThreadLock
{
    enum Straw { Long, Short }
}


next let's create a straws class, this would be our collection of straws that our threads are going to pull. we are going to leverage a class indexer for our straws class, we are going to create a get accessor that removes the straw from the backing collection and resizes it.

using System;
using System.Linq;
using System.Threading;

namespace pc.ThreadLock
{
    enum Straw { Long, Short }

    class Straws
    {
        public int Count { get { return _straws.Count(); } }
        Straw[] _straws;

        public Straw this[int Index]
        {
            get
            {
                var pulledStraw = _straws[Index];

                //shift straws from pulled one left and resize array
                for (; Index + 1 < _straws.Length; Index++)
                    _straws[Index] = _straws[Index + 1];
                Array.Resize(ref _straws, _straws.Length - 1);

                return pulledStraw;
            }
        }

        public Straws(int NumOfStraws, int NumOfShortStraws = 1)
        {
            _straws = new Straw[NumOfStraws];

            while (NumOfShortStraws > 0)
            {
                //pick random index for short straw, if selected index
                //already has short straw, try again.
                var shortStrawIndex = Program.rnd.Next(NumOfStraws);

                if (_straws[shortStrawIndex] == Straw.Short)
                    continue;

                _straws[shortStrawIndex] = Straw.Short;
                NumOfShortStraws--;
            }
        }
    }
}


Finally we are going to create our threads inside the main method which are going to pull the straws from our straw class.

class Program
{
    public static Random rnd = new Random(DateTime.Now.Millisecond);
    static Straws Straws = new Straws(5,2);

    static void Main(string[] args)
    {
        var Pawel = new Thread(new ThreadStart(PullStraw));
        Pawel.Name = "Pawel";

        var Magda = new Thread(new ThreadStart(PullStraw));
        Magda.Name = "Magda";

        var Jakub = new Thread(new ThreadStart(PullStraw));
        Jakub.Name = "Jakub";

        var Tomek = new Thread(new ThreadStart(PullStraw));
        Tomek.Name = "Tomek";

        var Marin = new Thread(new ThreadStart(PullStraw));
        Marin.Name = "Marin";

        var Threads = new Thread[] { Pawel, Magda, Jakub, Tomek, Marin };

        foreach (var t in Threads)
            t.Start();
    }

    static void PullStraw()
    {
        Straw PulledStraw = Straws[rnd.Next(Straws.Count - 1)];
        var output = $"{Thread.CurrentThread.Name} pulled {PulledStraw.ToString()}";
        Console.WriteLine(output);
    }

}

now if we run our application the way it currently sits we are going to notice some bizarre behavior, from too many short straws, to no short straws, to index out of bounds exceptions, this is because we have 5 threads manipulating the same straws data source at the same time. To fix our concurrency issues we need to lock the access to our straws class down so that each thread can access it individually and keep it from being corrupted, by multiple threads trying to make changes simultaneously.

to do this is actually pretty straight forward we are going to need an object to use as a lock outside of our threads scope, so at the program class level is fine.

using System;
using System.Linq;
using System.Threading;

namespace pc.ThreadLock
{
    enum Straw { Long, Short }
    class Straws
    {
        public int Count { get { return _straws.Count(); } }
        Straw[] _straws;

        public Straw this[int Index]
        {
            get
            {
                var pulledStraw = _straws[Index];

                //shift straws from pulled one left and resize array
                for (; Index + 1 < _straws.Length; Index++)
                    _straws[Index] = _straws[Index + 1];
                Array.Resize(ref _straws, _straws.Length - 1);

                return pulledStraw;
            }
        }

        public Straws(int NumOfStraws, int NumOfShortStraws = 1)
        {
            _straws = new Straw[NumOfStraws];

            while (NumOfShortStraws > 0)
            {
                //pick random index for short straw, if selected index
                //already has short straw, try again.
                var shortStrawIndex = Program.rnd.Next(NumOfStraws);
                if (_straws[shortStrawIndex] == Straw.Short)
                    continue;
                _straws[shortStrawIndex] = Straw.Short;
                NumOfShortStraws--;
            }
        }
    }

    class Program
    {
        public static Random rnd = new Random(DateTime.Now.Millisecond);
        static Straws Straws = new Straws(5, 2);

        //Our lock to prevent concurrent access to the straws object
        static object _lock = new object();

        static void Main(string[] args)
        {
            var Pawel = new Thread(new ThreadStart(PullStraw));
            Pawel.Name = "Pawel";

            var Magda = new Thread(new ThreadStart(PullStraw));
            Magda.Name = "Magda";

            var Jakub = new Thread(new ThreadStart(PullStraw));
            Jakub.Name = "Jakub";

            var Tomek = new Thread(new ThreadStart(PullStraw));
            Tomek.Name = "Tomek";

            var Marin = new Thread(new ThreadStart(PullStraw));
            Marin.Name = "Marin";

            var Threads = new Thread[] { Pawel, Magda, Jakub, Tomek, Marin };

            foreach (var t in Threads)
                t.Start();
        }

        static void PullStraw()
        {
            Straw PulledStraw;

            //restrict access to just one thread at a time
            lock (_lock)
            {
                PulledStraw = Straws[rnd.Next(Straws.Count - 1)];
            }

            var output = $"{Thread.CurrentThread.Name} pulled {PulledStraw.ToString()}";
            Console.WriteLine(output);
        }
    }
}


and that's it we've implemented locking for our threads