Thread는 하나의 프로세스에서 실행되는 작업의 단위이다. Thread를 사용하면 여러 작업을 동시에 실행할 수 있다. Thread를 사용하면 여러 작업을 동시에 실행할 수 있지만, Thread를 생성하고 관리하는 것은 비용이 많이 든다. 그래서 .NET에서는 Thread Pool을 제공한다.
Thread Pool
Thread Pool은 Thread를 관리하는 기능을 제공한다. Thread Pool을 사용하면 Thread를 생성하고 관리하는 비용을 줄일 수 있다. Thread Pool은 Thread를 생성하고 관리하는 것을 자동으로 처리한다. Thread Pool을 사용하면 Thread를 생성하고 관리하는 것을 자동으로 처리하기 때문에 Thread를 사용할 때보다 더 적은 비용으로 Thread를 사용할 수 있다.
webapi
프로젝트마다 다르겟지만 dotnet으로 webapi를 사용시 kestrel이라는 웹서버를 사용한다. kestrel은 기본적으로 thread pool을 사용한다. 확인해보자.
실제 서비스환경에서는 컨테이너를 사용한다. 이제 지금까지 프로그램을 컨테이너로 만들고 활용해보자.
cpu를 1개만 할당을 하였다. 이제 컨테이너를 실행해보자.
이제 locust를 실행해보자. 그리고 로그를 봐보자.
그리고 모니터링도 해보자.
alt text
잘 보인다.
테스트 결과
cpu를 1개만 할당하고 그리고 12개를 할당하고 테스트해보니 다음과 같다.
cpu
maxWorkerThread
usedWorkerThread
AvailableWorkerThread
1
32767
1
32766
12
32767
2
32765
cpu 갯수와 상관이 없이 Thread는 32767개로 고정되어있는것을 확인할수 있다.
쓰레드에 기본 갯수가 있고 그게 넘어가면 자동으로 생성을 하는것으로 알고 있다. 그럼 기본값이 다른것일가?
코드에 다음을 추가하고 실행해보자.
minWorker를 확인해보자.
cpu
minWorker
1
1
12
12
minWorker는 cpu 갯수와 같은것을 확인할수 있다.
cpu를 1개로 하고 minworker를 1000개로 해볼가?
다시 실행해보자.
alt text
기본적으로 1000개의 쓰레드를 가지고 있으므로 usedWorkerThread가 1000개로 나온다.
리퀘스트가 생김으로써 쓰레드가 늘어나는것을 확인할수 없다. 1000이 넘으면 증가를 할거 같다.
alt text
1000이 넘어가면 큐에 들어가고 하나씩 생성이 됨을 알수 있다.
다시 고민
친구의 문제는 쓰레드가 생기는게 오래 걸린다는것이다. 그래서 쓰레드를 미리 만들어두는것이 좋을거 같다. 그런데 아무리 테스트를 해봐도 간단한 작업은 Thread가 만들어지는것이 처음 10개와 1000개의 차이가 없다. 아무래도 쓰레드의 생성이 문제는 아닌거 같다. 그럼 다른것을 찾아봐야겠다.
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
namespace ThreadTest.Controllers;
[ApiController]
[Route("values")]
public class ValuesController : ControllerBase
{
private readonly ILogger<ValuesController> _logger;
public ValuesController(ILogger<ValuesController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetValues")]
public int Get()
{
Console.WriteLine($"The number of processors on this computer is {Environment.ProcessorCount}.");
var maxWorkerThreads = 0;
var maxCompletionPortThreads = 0;
ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads);
Console.WriteLine("Maximum worker threads: {0}", maxWorkerThreads);
var availableWorkerThreads = 0;
var completionPortThreads = 0;
ThreadPool.GetAvailableThreads(out availableWorkerThreads, out completionPortThreads);
Console.WriteLine($"Available Worker threads: {availableWorkerThreads}", availableWorkerThreads);
var usedWorkerThread = maxWorkerThreads - availableWorkerThreads;
Console.WriteLine($"Used worker threads: {usedWorkerThread}");
Thread.Sleep(10000); // Sleep for 10 seconds
return usedWorkerThread;
}
}
dotnet run
curl http://localhost:5007/values
The number of processors on this computer is 12.
Maximum worker threads: 32767
Available Worker threads: 32765
Used worker threads: 2
dotnet tool install --global dotnet-counters
dotnet-counters monitor -n ThreadTest # 프로세스 이름
pip install locust
mkdir locust
cd locust
cat > locust.py <<EOF
import time
from locust import HttpUser, between, task
class WebsiteUser(HttpUser):
wait_time = between(1, 2)
def on_start(self):
self.client.verify = False
@task
def launch(self):
self.client.get(url="/values")
EOF
var usedWorkerThread = maxWorkerThreads - availableWorkerThreads;
Console.WriteLine($"Used worker threads: {usedWorkerThread}");
# 이거 추가
int minWorker, minIOC;
// Get the current settings.
ThreadPool.GetMinThreads(out minWorker, out minIOC);
Console.WriteLine("Minimum worker threads: {0}", minWorker);
Thread.Sleep(1000000); // Sleep for 10 seconds
Console.WriteLine("Thread completed. go back to the pool.");
return usedWorkerThread;