Just for fun, I wrote this code to simulate a deadlock. Then, I sat and watched it run patiently until the total number of available worker threads that the thread pool had went down to zero. I was curious to see what would happen. Would it throw an exception?
using System;
using System.Diagnostics;
using System.Threading;
namespace Deadlock
{
class Program
{
private static readonly object lockA = new object();
private static readonly object lockB = new object();
static void Main(string[] args)
{
int worker, io;
ThreadPool.GetAvailableThreads(out worker, out io);
Console.WriteLine($"Total number of thread pool threads: {worker}, {io}");
Console.WriteLine($"Total threads in my process: {Process.GetCurrentProcess().Threads.Count}");
Console.ReadKey();
try
{
for (int i = 0; i < 1000000; i++)
{
AutoResetEvent auto1 = new AutoResetEvent(false);
AutoResetEvent auto2 = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(ThreadProc1, auto1);
ThreadPool.QueueUserWorkItem(ThreadProc2, auto2);
var allCompleted = WaitHandle.WaitAll(new[] { auto1, auto2 }, 20);
ThreadPool.GetAvailableThreads(out worker, out io);
var total = Process.GetCurrentProcess().Threads.Count;
if (allCompleted)
{
Console.WriteLine($"All threads done: (Iteration #{i + 1}). Total: {total}, Available: {worker}, {io}\n");
}
else
{
Console.WriteLine($"Timed out: (Iteration #{i + 1}). Total: {total}, Available: {worker}, {io}\n");
}
}
Console.WriteLine("Press any key to exit...");
}
catch(Exception ex)
{
Console.WriteLine("An exception occurred.");
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
Console.WriteLine("The program will now exit. Press any key to terminate the program...");
}
Console.ReadKey();
}
static void ThreadProc1(object state)
{
lock(lockA)
{
Console.WriteLine("ThreadProc1 entered lockA. Going to acquire lockB");
lock(lockB)
{
Console.WriteLine("ThreadProc1 acquired both locks: lockA and lockB.");
//Do stuff
Console.WriteLine("ThreadProc1 running...");
}
}
if (state != null)
{
((AutoResetEvent)state).Set();
}
}
static void ThreadProc2(object state)
{
lock(lockB)
{
Console.WriteLine("ThreadProc2 entered lockB. Going to acquire lockA.");
lock(lockA)
{
Console.WriteLine("ThreadProc2 acquired both locks: lockA and lockB.");
// Do stuff
Console.WriteLine("ThreadProc2 running...");
}
}
if (state != null)
{
((AutoResetEvent)state).Set();
}
}
}
}
Meanwhile, I also kept the Windows Task Manager's Performance tab running and watched the total number of operating system threads go up as my program ate up more threads.
Here is what I observed:
The OS didn't create more threads as the .NET thread pool created a thread every time. In fact, for every four or five iterations that my
for
loop ran, the OS thread-count would go up by one or two. This was interesting, but this isn't my question. It proves what has already been established.More interestingly, I observed that the number of threads did not decrease by 2 on every iteration of my
for
loop. I expected that it should have gone down by 2 because none of my deadlocked threads are expected to return since they are deadlocked, waiting on each other.I also observed that when the total number of available worker threads in the thread pool went down to zero, the program still kept running more iterations of my for-loop. This made me curious as to where those new threads were coming from if the thread pool had already run out of threads and none of the threads had returned?
So, to clarify, my two question(s), which, perhaps are related in that a single answer may be the explanation to them, are:
When a single iteration of my for-loop ran, for some of those iterations, no thread pool threads were created. Why? And where did the thread pool get the threads to run these iterations on?
Where did the thread pool get the threads from when it ran out of its total number of available worker threads and still kept running my
for
loop?
Monitor.Enter
logic allow the locked thread to be reused. Why not? Thread is waiting anyway, let's run another job in it, etc. – Sinatr