Search code examples
c#multithreadingdeadlockwaitpulse

Multithreading deadlock on a homemade threading pool in C#


i apologize for the fact that i am posting here this long code, but i have no other way to help you reproduce my issue. The code i give you of course is a miniature of what i am using. I know i could be using QueueUserWorkItem and i was using it until recently, but i realized my threads are so short that that my ThreadDispatcher method did not launch a second one before the first one finished. So i am trying to see if this way of doing it, is faster or not. The issue is i have a deadlock and i really cant understand why. I am posting code that you can directly compile and reproduce the issue.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace TestWhatever
{
    public class Program
    {
        static MyClass ThisClass = new MyClass();
        static void Main()
        {
            ThisClass.Start();
            for (int Cnt = 0; Cnt < 5; Cnt++)
            {
                Console.WriteLine("Launching Frame " + Cnt);
                ThisClass.Update();//Frames loop
            }
        }
    }

    public class MyClass
    {
        Random MyRandom = new Random();

        public static object _MultiDispatcherLocker = new object();
        public static bool MonoDispacherThreadLocked = true;
        public static bool MultiDispacherThreadLocked = true;
        // Thread pool in multithreading case
        private int MaxThreadsInParallel = 5;
        private static int MaxNumOfPool = 20;
        private Thread[] ThreadsPool;
        public static object _ThreadLockerList;
        private Func<string>[] ThreadFunctions;
        public bool[] ThreadLockedBools = Enumerable.Repeat(true, MaxNumOfPool).ToArray();


        public void Start()
        {
            StartThreads();
        }

        public void Update()
        {

            lock (_MultiDispatcherLocker)
            {
                MultiDispacherThreadLocked = false;
                Monitor.Pulse(_MultiDispatcherLocker);
            }
            Thread.Sleep(1000);
        }

        private void StartThreads()
        {
            ThreadsPool = new Thread[MaxNumOfPool];
            _ThreadLockerList = new object();
            ThreadFunctions = new Func<string>[MaxNumOfPool];
            ThreadLockedBools = new bool[MaxNumOfPool];

            for (int Cnt = 0; Cnt < MaxNumOfPool; Cnt++)
            {
                Console.WriteLine("Preparing ThreadID: " + Cnt);
                ThreadLockedBools[Cnt] = true;
                ThreadsPool[Cnt] = new Thread(new ParameterizedThreadStart(LaunchThread));
                ThreadsPool[Cnt].Start(Cnt);
            }

            Thread ThreadedMainThread = new Thread(new ThreadStart(ThreadDispatcher));
            ThreadedMainThread.Start();
            ThreadedMainThread.Priority = System.Threading.ThreadPriority.Highest;
        }

        private void LaunchThread(object iThreadID)
        {
            int ThreadID = (int)iThreadID;
            lock (_ThreadLockerList)
            {
                while (ThreadLockedBools[ThreadID])
                    Monitor.Wait(_ThreadLockerList);
            }
            while (true)
            {
                Console.WriteLine("Starting ThreadID: " + ThreadID);
                ThreadFunctions[ThreadID]();
                Console.WriteLine("Ending ThreadID: " + ThreadID);
                lock (_MultiDispatcherLocker)
                {
                    ThreadLockedBools[ThreadID] = true;
                    MultiDispacherThreadLocked = false;
                    Monitor.Pulse(_MultiDispatcherLocker);
                }
                lock (_ThreadLockerList)
                {
                    Console.WriteLine("Blocking ThreadID: " + ThreadID);
                    while (ThreadLockedBools[ThreadID])
                        Monitor.Wait(_ThreadLockerList);
                }
            }
        }


        private void ThreadDispatcher()//object Obj)
        {
            lock (_MultiDispatcherLocker)
            {
                while (MultiDispacherThreadLocked)
                    Monitor.Wait(_MultiDispatcherLocker);
            }
            while (true)
            {

                for (int Cnt = 0; Cnt < 20; Cnt++)//Threads loop
                {

                    if (RunningThreads() < MaxThreadsInParallel)
                    {
                        int CurrIntTest = MyRandom.Next(100000, 10000000);

                        int ThreadID = GetNextEmptyThread();


                        ThreadFunctions[ThreadID] = () => { MyMethodInThread(CurrIntTest); return null; };
                        lock (_ThreadLockerList)
                        {
                            ThreadLockedBools[ThreadID] = false;
                            Monitor.Pulse(_ThreadLockerList);
                        }
                    }
                    else//wait until someone ends
                    {
                        lock (_MultiDispatcherLocker)
                        {
                            while (MultiDispacherThreadLocked)
                            {
                                Monitor.Wait(_MultiDispatcherLocker);
                            }
                        }
                    }
                }

                lock (_MultiDispatcherLocker)
                {
                    MultiDispacherThreadLocked = true;
                    while (MultiDispacherThreadLocked)
                        Monitor.Wait(_MultiDispatcherLocker);
                }
            }
        }

        private void MyMethodInThread(int Counter)
        {
            List<string> MyDummy = new List<string>();
            for (int Cnt = 0; Cnt < Counter; Cnt++) MyDummy.Add("Dummy");
        }

        private int RunningThreads()
        {
            int ToReturn = 0;
            for (int Cnt = 0; Cnt < MaxThreadsInParallel; Cnt++)
            {
                if (!ThreadLockedBools[Cnt] || ThreadsPool[Cnt].ThreadState != System.Threading.ThreadState.WaitSleepJoin)
                    ToReturn++;
            }
            return ToReturn;
        }

        private int GetNextEmptyThread()
        {
            for (int Cnt = 0; Cnt < MaxThreadsInParallel; Cnt++)
            {
                if (ThreadLockedBools[Cnt] && ThreadsPool[Cnt].ThreadState == System.Threading.ThreadState.WaitSleepJoin)
                    return Cnt;
            }
            return -1;
        }

    }
}

it would be really awesome if you could help me with this.


Solution

  • Let see what you've got.

    LaunchThread (multiple threads):

    lock (_ThreadLockerList)
    {
        while (ThreadLockedBools[ThreadID])
            Monitor.Wait(_ThreadLockerList);
    }
    

    ThreadDispatcher (single thread):

    lock (_ThreadLockerList)
    {
        ThreadLockedBools[ThreadID] = false;
        Monitor.Pulse(_ThreadLockerList);
    }
    

    The thread released by the Monitor.Pulse call may not be the one with ThreadLockedBools[ThreadID] = false, in which case it will immediately enter the Monitor.Wait again, thus effectively eating the signal.

    To fix the problem (and in general in such scenarios), use Monitor.PulseAll instead.