[로봇활용_14주차] C# 스레드 풀(Thread Pool)

최윤호·2025년 11월 7일
post-thumbnail

스레드를 관리하는 효율적인 방법

우리 만든 C# 프로그램은 '독립된 공장'프로세스와,
그 안에서 실제 일을 하는 '일꾼'스레드가 있습니다.
그런데 만약에 아주 간단하고 짧은 작업이 수시로 쏟아져 들어온다면 어떨까요?
예를 들어, '나사 하나 조이기', '스티커 한 장 붙이기' 같은 작업이죠.
이런 사소한 일을 처리할 때마다 매번 새로운 일꾼(스레드)을
고용(new Thread())하는 것은 너무 비효율적이지 않을까요?
바로 이 문제를 해결하기 위해 등장한 개념이 스레드 풀(Thread Pool)입니다!

1)스레드 풀(Thread Pool)이란?

스레드 풀은 이름 그대로 '스레드(일꾼)들의 모음(Pool)'입니다.
필요할 때마다 스레드를 새로 생성하고 파괴하는 대신,
미리 일정 수의 스레드를 만들어 놓고 '풀(Pool)'에 대기시켜 둡니다.
그리고 작업이 발생하면, 스레드 하나를 꺼내서 작업을 할당하는 방식이죠.

스레드 풀은 마치 '상시 대기 중인 인력사무소'와 같습니다.

  • 사전 채용: 인력사무소는 미리 여러 명의 일꾼(스레드)을 채용해 놓고 대기시킵니다.
  • 신속한 투입: 공장에서 "나사 조일 사람 한 명 보내주세요!"라고 요청하면,
    인력사무소는 대기 중인 일꾼을 즉시 보내줍니다. (스레드는 새로 생성 안 함)
  • 재사용: 일을 마친 일꾼은 해고되는 것이 아니라,
    다시 인력사무소로 돌아와 다음 일을 기다립니다. (스레드 재사용)
  • 인원 관리: 공장에서 갑자기 100명의 일꾼을 요청해도,
    인력사무소는 보유한 인원(최대 스레드 수) 내에서만 보내줍니다. (과부하 방지)

스레드 풀을 사용하면 스레드를 생성하고 제거하는 데 드는 비용(오버헤드)을 줄이고,
프로그램의 전반적인 성능, 자원 효율성, 시스템 안정성이 향상됩니다.

2)C#에서 스레드 풀 사용

C#에서는 ThreadPool클래스를 통해 간단하게 스레드 풀을 사용할 수 있습니다.
new Thread()로 직접 스레드를 만드는 것보다 훨씬 간편하죠.
여기서는 전통적인 ThreadPool.QueueUserWorkItem메서드를 사용합니다.
"사용자의 작업 항목을 대기열(Queue)에 추가해줘!"라는 뜻으로,
스레드 풀에 "이 일 좀 해주세요!" 하고 작업을 맡기는 것과 같습니다.

[코드]

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        for (int i = 0; i < 10; i++)
        {
            int taskNumber = i; // 클로저 문제를 피하기 위해 루프 변수를 복사
            // "이 DoWork라는 작업을 taskNumber와 함께 처리해줘!" 라고 스레드 풀에 요청
            // 최신 C# 환경에서는 Task.Run을 사용하는 것이 권장됩니다.
            ThreadPool.QueueUserWorkItem(DoWork, taskNumber); // 스레드 풀 학습 목적으로 사용
        }

        Console.WriteLine("모든 작업을 스레드 풀에 요청했습니다. 메인 스레드는 종료 대기 중...");
        Console.ReadLine(); // 프로그램이 바로 끝나지 않게 대기
    }

    static void DoWork(object? state)
    {
        if (state is int taskNumber)
        {
            Console.WriteLine($"작업 {taskNumber}번을 스레드 ID: " +
                $"{Thread.CurrentThread.ManagedThreadId}번이 처리 중입니다.");
            Thread.Sleep(1000); // 1초간 무언가 작업을 한다고 가정
        }
    }
}

[실행 결과]

작업 2번을 스레드 ID: 9번이 처리 중입니다.
모든 작업을 스레드 풀에 요청했습니다. 메인 스레드는 종료 대기 중...
작업 0번을 스레드 ID: 5번이 처리 중입니다.
작업 1번을 스레드 ID: 10번이 처리 중입니다.
작업 3번을 스레드 ID: 7번이 처리 중입니다.
작업 4번을 스레드 ID: 9번이 처리 중입니다.
작업 5번을 스레드 ID: 7번이 처리 중입니다.
작업 6번을 스레드 ID: 10번이 처리 중입니다.
작업 7번을 스레드 ID: 5번이 처리 중입니다.
작업 8번을 스레드 ID: 11번이 처리 중입니다.
작업 9번을 스레드 ID: 9번이 처리 중입니다.

코드를 실행해 보면, 여러 개의 스레드 ID가 뒤죽박죽 출력되는 것을 볼 수 있습니다.
스레드 풀이 알아서 여러 일꾼에게 작업을 분배하고 동시에 처리하고 있다는 증거죠!
new Thread().Start()를 10번 호출하는 것보다 훨씬 깔끔하고 효율적입니다.

3)스레드 풀은 만능일까요?

그렇다면 모든 작업을 스레드 풀에 맡기면 될까요? 다시 인력사무소 비유로 돌아가 봅시다.
만약 어떤 일꾼에게 '중요한 서류 결재'라는 '오래 걸리는 작업'을 시켰다고 가정합니다.
이 일꾼은 서류를 받아올 때까지 일이 '차단(Blocked)'이 된 상태가 되는 것이죠.
만약 인력사무소의 모든 일꾼이 이렇게 오래 걸리는 작업에 투입된다면 어떻게 될까요?
공장 내부에서 '나사 조이기' 같은 간단한 작업 요청이 와도 보내줄 일꾼이 없는
'스레드 풀 고갈(Thread Pool Starvation)' 상태에 빠지게 됩니다.
이처럼 스레드 풀은 '짧게 끝나는 단순한 작업'에 매우 효과적이지만,
'스레드를 오랫동안 점유하는 작업'에는 오히려 독이 될 수 있습니다!

4)정리

스레드 풀을 사용하려면 '어떤 종류의 작업을 맡길 것인가?'를 잘 판단해야 합니다.

항목설명
개념미리 생성해 둔 스레드 집합. 필요할 때마다 빌려 쓰고 반납하는 방식.
장점스레드 생성/소멸 오버헤드 감소, 자원 효율성, 안정성
주의점오래 걸리거나 차단(Blocking)되는 작업을 맡기면 성능 저하의 원인이 될 수 있음
profile
🚀 미래의 엔지니어를 꿈꾸는 훈련생의 기록 📝

0개의 댓글