[C#] 쓰레드 생성하기(Thread, Thread Pool, Task)

지즈·2025년 2월 7일

CSharp

목록 보기
1/3

쓰레드 기본 개념

  • 하나의 프로세스는 여러 개의 쓰레드를 가질 수 있습니다.
  • 같은 프로세스 안의 쓰레드들은 코드, 데이터, 힙 영역을 공유하되, 스택은 각각 따로 할당합니다.

C# 과 쓰레드

C# 에서는 쓰레드 객체를 생성할 때, 기본적으로 Foreground 쓰레드로 실행됩니다. Main 메서드가 종료되어도 해당 쓰레드가 끝날 때까지 프로그램은 종료되지 않습니다.

  • Background 쓰레드 : Main 메서드가 종료되면 남아 있는 작업과 관계 없이 프로그램은 즉시 종료됩니다.
  • isBackgorund 속성을 사용하여 해당 쓰레드를 Background 쓰레드로 변경할 수 있습니다.
Thread t = new Thread(PrintThread); // 쓰레드 생성

t.IsBackground = true; // Background 쓰레드로 설정
t.Start(); // 쓰레드 시작 

Console.WriteLine("Main");

void PrintThread()
{
    Console.WriteLine("Print Thread");
}

결과


C#의 쓰레드 풀

쓰레들 생성하는 건 비용이 매우 크기 때문에, 대신 쓰레드 풀을 사용하곤 합니다. 쓰레드 풀은 매번 새로운 쓰레드를 생성하지 않고, 미리 만들어진 쓰레드들을 재사용하여 생성 비용을 절감합니다. 짧고 반복적인 작업을 할 때 매우 유리합니다.

Thread Pool의 특징

  • Background : 기본적으로 Background이며, Main 종료 시 프로그램이 종료된다.
  • 생성할 수 있는 개수에 제한이 있어 작업이 쓰레드 수보다 많다면, 작업을 끝내고 돌아온 쓰레드를 다시 내보내는 형식입니다. 그러므로 짧고 반복되는 작업에 적합합니다.

ThreadPool 의 모든 쓰레드가 사용 중인 경우

ThreadPool.SetMinThreads(1, 1); // 최소 한 개의 쓰레드는 생성됨
ThreadPool.SetMaxThreads(5, 5); // 최대 다섯 개의 쓰레드만 생성됨

// 5개의 스레드 모두에게 무한 루프를 도는 작업 실행시키기
for (int i = 0; i < 5; i++)
{
    ThreadPool.QueueUserWorkItem(ThreadPoolTest1);
}

 **// Thread Pool로 돌아오는 쓰레드가 없으므로, 아래 쓰레드는 절대 실행되지 않는다.** 
ThreadPool.QueueUserWorkItem(ThreadPoolTest2);

while(true) {} // 쓰레드풀은 Background이므로, Main이 종료되지 않기 위해 무한 루프 실행

// ===============================================

// Thread Pool의 쓰레드가 실행할 함수 1, 2
void ThreadPoolTest1(object state)
{
    Console.WriteLine("양보하지 않는 쓰레드");
    while (true) { }; // 쓰레드를 양보하지 않게 한다.
}

void ThreadPoolTest2(object state)
{
    Console.WriteLine("ThreadPool");
}

결과

이 코드에서는 쓰레드 풀의 최대 개수를 5개로 설정하고, 5개의 쓰레드 모두에게 무한 루프를 도는 작업을 실행시켰습니다. 결국 작업을 마치고 쓰레드 풀로 돌아오는 쓰레드가 없게 되어, ThreadPoolTest2 는 영영 실행되지 않습니다.


이제 4개의 쓰레드에게만 ThreadPoolTest1을 실행시켜보겠습니다.

ThreadPool.SetMinThreads(1, 1); // 최소 한 개의 쓰레드는 생성됨
ThreadPool.SetMaxThreads(5, 5); // 최대 다섯 개의 쓰레드만 생성됨

// 4개의 스레드에게만 무한 루프를 도는 작업 실행시키기
for (int i = 0; i < 4; i++)
{
    ThreadPool.QueueUserWorkItem(ThreadPoolTest1);
}

 **// Thread Pool에 남은 하나의 쓰레드가 다음 작업을 실행한다.**
ThreadPool.QueueUserWorkItem(ThreadPoolTest2);

while(true) {} // 쓰레드풀은 Background이므로, Main이 종료되지 않기 위해 무한 루프 실행

// ===============================================

// Thread Pool의 쓰레드가 실행할 함수 1, 2
void ThreadPoolTest1(object state)
{
    Console.WriteLine("양보하지 않는 쓰레드");
    while (true) { }; // 쓰레드를 양보하지 않게 한다.
}

void ThreadPoolTest2(object state)
{
    Console.WriteLine("ThreadPool");
}

결과
4개의 쓰레드는 ThreadPoolTest1을 실행하지만, 남은 하나의 쓰레드는 ThreadPoolTest2를 실행하는 걸 확인할 수 있습니다.


TaskCreationOptions.LongRunning : 쓰레드풀의 개수 제한을 넘어 작업을 수행하고 싶을 때

TaskCreationOptions.LongRunninong

네트워크 요청, 파일 다운로드 등 긴 시간 동안 실행되는 작업에서 사용됩니다. ThreadPool 과 별개로 새로운 스레드를 생성하여 독립적으로 실행됩니다.

ThreadPool.SetMinThreads(1, 1); // 최소 한 개의 쓰레드는 생성됨
ThreadPool.SetMaxThreads(5, 5); // 최대 다섯 개의 쓰레드만 생성됨

// 쓰레드 풀과 독립적으로 실행될 Task
for (int i = 0; i < 5; i++)
{
    Task t = new Task(() => { Console.WriteLine("Task");  }, TaskCreationOptions.LongRunning);
    t.Start();
}

// 5개의 스레드 모두에게 Work를 실행시키기
for (int i = 0; i < 5; i++)
{
    ThreadPool.QueueUserWorkItem(ThreadPoolTest1);
}

// 쓰레드풀은 Background이므로, Main이 종료되지 않기 위해 무한 루프 실행
while (true) { };

// ===============================================

// 쓰레드 풀이 실행할 함수 1, 2
void ThreadPoolTest1(object state)
{
    Console.WriteLine("양보하지 않는 쓰레드");
    while (true) { }; // 쓰레드를 양보하지 않게 한다.
}

결과

쓰레드 풀과는 독립적으로 실행 중인 Task를 확인할 수 있습니다.

profile
클라이언트 개발자가 되는 그 날까지 킵 고잉

0개의 댓글