3

I am trying to figure out exactly what impact does ThreadPool.SetMinThreads makes.

According to official documentation it says

Sets the minimum number of threads the thread pool creates on demand, as new requests are made, before switching to an algorithm for managing thread creation and destruction.

In my understanding, as a developer, I'm suppose to have control over mechanism on how to spin new threads on demand, so they are created and waiting in the idle state, in situations when for example I'm expecting load of request coming at specific time.

And this is exactly what I initially thought SetMinThreads method is designed for.

But when I started actually playing with it - I got really weird results.

So I'm having my ASP.NET .NET5 application, and in controller action I'm having code like this: ThreadPool.SetMinThreads(32000, 1000);

And of course I'm intuitively expecting runtime to create 32K of worker threads and 1000 io threads for me.

And when I do that, and then call other method - Process.GetCurrentProcess().Threads to get all the process' threads, and print statistic on them, I get something like this

Standby - 17
Running - 4

I thought that maybe app needs some time to spin new threads, so I've tried diffrent delays, 1min, 5min and 10mins.

But result always stays the same, I get 15-20 Standby and 2-4 Running.

So then comes logical question - what exactly SetMinThreads method is doing at all? The description provided by MSDN does not seem very helpful.

And another logical question - what if I wanted to force dotnet to spin 32K of new threads in idle state - does dotnet provide any mechanism for it at all?

Prostakov
  • 910
  • 1
  • 12
  • 22
  • 1
    The odds that your computer is even capable of making that many threads is *very* low. The odds that it would be a good idea if you somehow had a computer that *could* actually run them is much lower still. If you want to micromanage your threads don't use a thread pool, the whole point of a thread pool is to *not* explicitly manage the threads. That's the pool's job. – Servy Aug 28 '21 at 14:45
  • Yeah, I guess that makes sense... I guess I just had no understanding that thousands of threads can be created relatively quickly, so my case with sudden load - it can be handled, and there's no need to create idle threads ahead of time. – Prostakov Aug 29 '21 at 14:02

1 Answers1

5

The ThreadPool.SetMinThreads sets the minimum number of threads that the ThreadPool creates instantly on demand. That's the key phrase, and it is indeed quite unintuitive. The ThreadPool currently¹ (.NET 5) works in two modes:

  1. When a new request for work arrives, and all the threads in the pool are busy, create instantly a new thread in order to satisfy the request.

  2. When a new request for work arrives, and all the threads in the pool are busy, queue the request, and wait for 1 sec before creating a new thread, hoping that in the meantime one of the worker threads will complete its current work, and will become available for serving the queued request.

The ThreadPool.SetMinThreads sets the threshold between these two modes. It does not give you control over the number of threads that are alive right now. Which is not very satisfying, but it is what it is. If you want to force the ThreadPool to create 1,000 threads instantly, you must also sent an equal number of requests for work, additionally to calling ThreadPool.SetMinThreads(1000, 1000). Something like this should do the trick:

ThreadPool.SetMinThreads(1000, 1000);
Task[] tasks = Enumerable.Range(0, 1000)
    .Select(_ => Task.Run(() => Thread.Sleep(100)))
    .ToArray();

Honestly I don't think that anyone does that. Creating a new Thread is quite fast in human time (it requires around 0.25 milliseconds per thread in my PC), so for a system that receives requests from humans, the overhead of creating a thread shouldn's have any measurable impact. On the other hand 0.25 msec is an eon in computer time, when you want a thread to do a tiny amount of work (in the range of nanoseconds), like adding something in a List<T>. That's why the ThreadPool was invented in the first place: To amortize the overhead of thread-creation for tiny but numerous workloads.

Be aware that creating a new Thread has also a memory cost, which is generally more significant than the time cost: each thread requires at least 1 MB of RAM for its stack. So creating 32,000 threads will tie down 32 GB of memory just for stack space. This is not very efficient. That's why in recent years asynchronous programming has become so prominent in server-side web development, because it allows to do more work with less threads.

¹ There is nothing preventing the Microsoft engineers from changing/twicking the implementation of the ThreadPool in the future. AFAIK this has already happened at least once in the past.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    As far as I could measure it the delay is 1s between two threads. Where did you get the 500ms from? With .NET 6 there is a tweak to spin up new threads much faster if all threads a blocked waiting for a Task.Result. – Alois Kraus Aug 28 '21 at 21:00
  • @AloisKraus [you are right](https://dotnetfiddle.net/1fr8E1), it's 1 sec. I could swear that it is 500 msec, based on numerous past observations. Probably I've misinterpreted many of my past experiments. – Theodor Zoulias Aug 28 '21 at 21:26
  • 1
    Let me get this straight. So if I have min worker threads set to 1000, and I currently have 5 worker threads - we will be working in mode #1 and will be spinning new thread for each request. But once I reach 1000 worker threads in my threadpool - we will switch to mode #2, and each new request will wait for a second for thread from threadpool - and only then create new thread. Is that correct idea? – Prostakov Aug 29 '21 at 09:29
  • @Prostakov yeap, AFAIK that's exactly how it works. – Theodor Zoulias Aug 29 '21 at 10:06
  • 1
    @TheodorZoulias Regarding the 500ms -- you probably remember it from the [documentation](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-management-faq#important-details-about-threadpool-growth)! Microsoft states: "...the ThreadPool will throttle the rate at which it injects new threads to one thread per 500 milliseconds" – PatrickSteele Aug 07 '23 at 15:22
  • @PatrickSteele good find! – Theodor Zoulias Aug 07 '23 at 16:23