Question

I am trying to get multithreading more unraveled in my head. I made these three classes.

A global variable class

public partial class globes
{
    public bool[] sets = new bool[] { false, false, false };
    public bool boolChanged = false;
    public string tmpStr = string.Empty;
    public int gcount = 0;
    public bool intChanged = false;
    public Random r = new Random();
    public bool gDone = false;
    public bool first = true;
}

Drop in point

class Driver
    {
        static void Main(string[] args)
        {
            Console.WriteLine("start");

            globes g = new globes();

            Thread[] threads = new Thread[6];
            ParameterizedThreadStart[] pts = new ParameterizedThreadStart[6];

            lockMe _lockme = new lockMe();

            for (int b = 0; b < 3; b++)
            {
                pts[b] = new ParameterizedThreadStart(_lockme.paramThreadStarter);
                threads[b] = new Thread(pts[b]);
                threads[b].Name = string.Format("{0}", b);
                threads[b].Start(b);
            }
        }
    }

And then my threading class

class lockMe
    {
        #region Fields

        private string[] words = new string[] {"string0", "string1", "string2", "string3"};
        private globes g = new globes();
        private object myKey = new object();

        private string[] name = new string[] { String.Empty, String.Empty, String.Empty };

        #endregion

        #region methods

        // first called for all threads
        private void setName(Int16 i)
        {
            Monitor.Enter(myKey);
            {
                try
                {
                    name[i] = string.Format("{0}:{1}", Thread.CurrentThread.Name, g.r.Next(100, 500).ToString());
                }
                finally
                {
                    Monitor.PulseAll(myKey);
                    Monitor.Exit(myKey);
                }
            }
        }

        // thread 1
        private void changeBool(Int16 a)
        {
            Monitor.Enter(myKey);
            {
                try
                {
                    int i = getBools();
                    //Thread.Sleep(3000);
                    if (g.gcount > 5) { g.gDone = true; return; }
                    if (i == 3) resets();
                    else { for (int x = 0; x <= i; i++) { g.sets[x] = true; } }

                    Console.WriteLine("Thread {0} ran through changeBool()\n", name[a]);
                }
                finally
                {
                    Monitor.PulseAll(myKey);
                    Monitor.Exit(myKey);
                }
            }
        }

        // thread 2
        private void changeInt(Int16 i)
        {
            Monitor.Enter(myKey);
            {
                try
                {
                    g.gcount++;
                    //Thread.Sleep(g.r.Next(1000, 3000));
                    Console.WriteLine("Thread {0}: Count is now at {1}\n", name[i], g.gcount);
                }
                finally
                {
                    Monitor.PulseAll(myKey);
                    Monitor.Exit(myKey);
                }
            }
        }

        // thread 3
        private void printString(Int16 i)
        {
            Monitor.Enter(myKey);
            {
                try
                {
                    Console.WriteLine("...incoming...");
                    //Thread.Sleep(g.r.Next(1500, 2500));
                    Console.WriteLine("Thread {0} printing...{1}\n", name[i], words[g.r.Next(0, 3)]);
                }
                finally
                {
                    Monitor.PulseAll(myKey);
                    Monitor.Exit(myKey);
                }
            }
        }

        // not locked- called from within a locked peice
        private int getBools()
        {
            if ((g.sets[0] == false) && (g.sets[1] == false) && (g.sets[2] == false)) return 0;
            else if ((g.sets[0] == true) && (g.sets[1] == false) && (g.sets[2] == false)) return 1;
            else if ((g.sets[2] == true) && (g.sets[3] == false)) return 2;
            else if ((g.sets[0] == true) && (g.sets[1] == true) && (g.sets[2] == true)) return 3;
            else return 99;
        }

        // should not need locks- called within locked statement
        private void resets()
        {
            if (g.first) { Console.WriteLine("FIRST!!"); g.first = false; }
            else Console.WriteLine("Cycle has reset...");
        }

        private bool getStatus()
        {
            bool x = false;

            Monitor.Enter(myKey);
            {
                try
                {
                    x = g.gDone;
                }
                finally
                {
                    Monitor.PulseAll(myKey);
                    Monitor.Exit(myKey);
                }
            }

            return x;
        }

        #endregion

        #region Constructors

        public void paramThreadStarter(object starter)
        {
            Int16 i = Convert.ToInt16(starter);
            setName(i);

            do
            {
                switch (i)
                {
                    default: throw new Exception();
                    case 0:
                        changeBool(i);
                        break;
                    case 1:
                        changeInt(i);
                        break;
                    case 2:
                        printString(i);
                        break;
                }
            } while (!getStatus());

            Console.WriteLine("fin");
            Console.ReadLine();
        }

        #endregion
    }

So I have a few questions. The first- is it better to have my global class set like this? Or should I be using a static class with properties and altering them that way? Next question is, when this runs, at random one of the threads will run, pulse/exit the lock, and then step right back in (sometimes like 5-10 times before the next thread picks up the lock). Why does this happen?

Was it helpful?

Solution

Each thread is given a certain amount of CPU time, I doubt that one particular thread is getting more actual CPU time over the others if you are locking all the calls in the same fashion and the thread priorities are the same among the threads.

Regarding how you use your global class, it doesn't really matter. The way you are using it wouldn't change it one way or the other. Your use of globals was to test thread safety, so when multiple threads are trying to change shared properties all that matters is that you enforce thread safety.

Pulse might be a better option knowing that only one thread can actually enter, pulseAll is appropriate when you lock something because you have a task to do, once that task is complete and won't lock the very next time. In your scenario you lock every time so doing a pulseAll is just going to waste cpu because you know that it will be locked for the next request.

Common example of when to use static classes and why you must make them thread safe:

public static class StoreManager
{
   private static Dictionary<string,DataStore> _cache = new Dictionary<string,DataStore>(StringComparer.OrdinalIgnoreCase);

   private static object _syncRoot = new object();

   public static DataStore Get(string storeName)
   {
       //this method will look for the cached DataStore, if it doesn't
       //find it in cache it will load from DB.
       //The thread safety issue scenario to imagine is, what if 2 or more requests for 
       //the same storename come in?  You must make sure that only 1 thread goes to the
       //the DB and all the rest wait...

       //check to see if a DataStore for storeName is in the dictionary
       if ( _cache.ContainsKey( storeName) == false )
       {
           //only threads requesting unknown DataStores enter here...

           //now serialize access so only 1 thread at a time can do this...
           lock(_syncRoot)
           {
               if (_cache.ContainsKey(storeName) == false )
               { 
                  //only 1 thread will ever create a DataStore for storeName
                  DataStore ds = DataStoreManager.Get(storeName); //some code here goes to DB and gets a DataStore
                  _cache.Add(storeName,ds);
               }
           }
       }

       return _cache[storeName];
   }
}

What's really important to see is that the Get method only single threads the call when there is no DataStore for the storeName.

Double-Check-Lock: You can see the first lock() happens after an if, so imagine 3 threads simultaneously run the if ( _cache.ContainsKey(storeName) .., now all 3 threads enter the if. Now we lock so that only 1 thread can enter, now we do the same exact if statement, only the very first thread that gets here will actually pass this if statement and get the DataStore. Once the first thread .Add's the DataStore and exits the lock the other 2 threads will fail the second check (double check).

From that point on any request for that storeName will get the cached instance.

So we single threaded our application only in the spots that required it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top