skip to Main Content

Problem

I’ve a .Net7 console application, executed in parallel (many instances of the same program) on a multi-core linux server.
Apparently the server has a limit (ulimit -a) that limits the number of threads created per user. If the application wants to create more threads, an OutOfMemory exception is raised (https://github.com/dotnet/runtime/issues/71761).

How to reproduce the issue (on linux/wsl):

for (var i = 0; i < int.Parse(args[0]); i++)
{
    var newThread = new Thread(() => Thread.Sleep(10_000));
    newThread.Start();
    if((i+1)%1000 == 0) Console.WriteLine($"Threads: {i+1}");
}
Console.WriteLine("ctrl+c to kill");

and run

ulimit -u 4096

this set the limit to 4096. Then run

dotnet run 5000

or

dotnet run 2500 & dotnet run 2500 &

the code will try to create 5000 threads in a single process OR 5000 threads in two processes, but the system has 4096 as limit => OutOfMemory raised in both cases.

Consider to run 100 instances of the program… if the program tries to spawn 40 threads, an OutOfMemory exception is raised.

Desiderata

We are trying to increase such limit but I would like to know why so many threads are spawned (let’s use #ObsTh = Process.GetCurrentProcess().Threads.Length).
Info:

  • it’s a .Net7 console application, published in Release mode, linux-x64, standalone
  • the app does not start threads itself, it just uses async/await paradigm. first line of code in top level statment: #ObsTh=10 threads
  • the app perform few HttpClient.GetAsync() requests. The HttpClient is retrieved from IHttpClientFactory registered in the ServiceCollection. #ObsTh=22 threads (+12)
  • the app uses StackExchange.Redis. After connection, #ObsTh=32 threads (+10) in a dedicatedthreadpool.
  • sometimes the thread count reaches 35-40 threads and my application is "basically" a single thread application.

now… Excluding StackExchange.Redis (but if you know something let me know), how can I reduce the number of threads in my console app?


Just few lines of codes that shows the behavior (I’m excluding redis):

using System.Diagnostics;
ThreadPool.SetMaxThreads(1, 1);

Console.WriteLine($"Start process: {Process.GetCurrentProcess().Threads.Count}");

HttpClient c = new HttpClient();
var result = await c.GetAsync("https://www.bing.com");
result.EnsureSuccessStatusCode();
Console.WriteLine($"After http call: {Process.GetCurrentProcess().Threads.Count}");

If I run this program.cs with dotnet run (or after publish run) the result is:

Start process: 8
After http call: 22

so… just after few lines of code I have 8 threads, after httpcall 22. I just would like to reduce this number (if possible) because I’m running a lot of instances of this program.


Keep in mind: the problem is that linux has a limit and I have to reduce the amount of threads used by my app. No problems on windows or if I increase linux limit.
An instance of my application now runs for about 30-40 seconds:

  • Time spent for http calls: ~0.5 seconds
  • Time spent reading data from redis: ~3 seconds.
  • The remaining time is on CPU.

Rider, using above code, shows:

  • 3 Unknown ?
  • 3 ThreadPool Workers
  • 1 ThreadPool Wait
  • 1 ThreadPool IO
  • 1 ThreadPool Gate
  • 1 .NET Timer
  • 1 Main Thread
  • How to see the other 26-11=15 threads? and, more important, how to reduce them?

2

Answers


  1. Chosen as BEST ANSWER

    A partial response.

    I was not able to reduce threads spawned by .net (and threading pool) using ThreadPool.SetMaxThreads.

    It is possible to configure StackExchange.Redis to use threads in the ThreadPool and not a dedicated pool of threads (that is composed by 10 threads):

        var configurationOptions = new ConfigurationOptions
        {
            SocketManager = SocketManager.ThreadPool
        };
    

    So, I was able to reduce by 8 threads.


  2. Most of those are going to be thread pool threads, so ThreadPool.SetMaxThreads is your friend. .NET has historically targeted desktop/server environments with the intention of using the available computing resources, so targeting restrained execution environments does require tweaking.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search