본문 바로가기

C#

[C#] Thread

멀티 스레드에 대한 설명은 사실 예전에 C++로 설명하기 전에 간단하게 설명한 포스트가 있습니다. 이것을 간단하게 참조해주시면 되겠습니다. https://frokdevelop.tistory.com/43

 

[서버] 멀티스레드

멀티스레드는 서버 입장에서는 필수적인 요소임. 문제는 멀티스레드 프로그래밍을 하는 순간부터 굉장히 많은 문제가 생김. 예로 들어 내가 타이쿤 게임을 한다고 가정하자. 그리고 한식, 일식,

frokdevelop.tistory.com

 

위의 글을 간단하게 설명하자면 결국 컴퓨터가 한 번에 많은 일을 동시에 처리할 수 있게, 스레드라고 부르는 작은 프로세스에 일감을 나눠주는 식을 말하는 겁니다.

 

C#은 다행히도 최근 전까지만 해도(사실 좀 되긴 했지만) 그 OS에서 제공하는 API를 빠삭하게 알아야 했던 C++에 비해서는 굉장히 스레드 관리가 쉬운 편입니다. 나름 자바를 따라가려 했었고 이후로도 지속적인 개량이 이루어진 그런 언어이기 때문에 이런 개념에 대해서 충분히 쉽게 쓸 수 있고, 지금 와서는 저는 조심스럽게 자바보다도 이런 면에서도 그렇고 더 현대적인 언어라고 생각합니다. (주관적인 생각입니다!) - 참고 :  https://learn.microsoft.com/ko-kr/dotnet/csharp/whats-new/csharp-version-history

 

C#의 역사 - C# 가이드

이 언어의 초창기 버전은 어떤 모습이었으며 이후 어떻게 변했는가?

learn.microsoft.com

 

서론이 길었습니다. Thread의 경우에는 msdn(현재는 ms learn)에도 굉장히 잘 서술이 되어있는 편입니다. 이 예시 코드를 들고와서 설명을 해보겠습니다.

using System;
using System.Threading;

public class ThreadExample {
    public static void ThreadProc() {
        for (int i = 0; i < 10; i++) {
            Console.WriteLine("ThreadProc: {0}", i);
            Thread.Sleep(0);
        }
    }

    public static void Main() {
        Console.WriteLine("Main thread: Start a second thread.");
        
        Thread t = new Thread(new ThreadStart(ThreadProc));
        t.Start();
        //Thread.Sleep(0);

        for (int i = 0; i < 4; i++) {
            Console.WriteLine("Main thread: Do some work.");
            Thread.Sleep(0);
        }
    }
}

출처 - https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.thread?view=net-7.0 

 

Thread 클래스 (System.Threading)

스레드를 만들고 제어하며, 해당 속성을 설정하고, 상태를 가져옵니다.

learn.microsoft.com

 

우리는 Thread라는 직원을 고용하고, 이 직원에게 i를 0부터 9까지 콘솔에 출력하도록 했습니다. 이제 저 직원이 무한히 집에 못가게 일을 시켜봅시다.

public static void ThreadProc() {
   while(true)
      Console.WriteLine("I want to go home!");
}

이 상태로 실행하면 main함수는 끝이 나겠지만, 이 함수가 끝이 나지 않고 계속 실행되는 것을 확인할 수 있습니다.

그는 집을 갈 수 없습니다.

이를 통해서 C#의 스레드는 백그라운드에서 돌아가는 물건이 아닌 점을 확인할 수 있습니다. 이 스레드가 백그라운드에서 돌아가는 물건인지 아닌지는 다음과 같은 프로퍼티를 통해서 알 수 있습니다.

Console.WriteLine($"{t.IsBackground}");

즉 만약 main 함수가 끝나면 이 스레드가 끝나게 하려면 IsBackground를 true로 변경해주면 됩니다. 하지만 이런 생각도 들게 됩니다. '아! 나는 백그라운드가 main이 끝나도 계속 실행하게 하고 싶은데,,...' 이런 생각이 든다면 나오는 개념이 바로 join이란 메서드입니다.

t.join();

join을 넣고 실행을 하게 된다면, main함수가 join에서 대기를 하는 것을 볼 수 있습니다. 그리고 VS 기준으로 디버깅 도중에 일시 정지를 누르면 그 thread의 id나 그 외 정보들을 파악할 수 있게 됩니다.

현재 각 스레드가 어떤 것을 실행하는가를 알 수 있다.
지금 실행중인 thread의 id를 알 수 있다.


지금까지 한 것은 내가 일일히 직원을 고용하는 식이다. 하지만 생각을 해보자. 내가 편의점 주인이고 알바를 구하는데, 편의점 앞에 알바 공고를 붙여도 물론 고용은 되겠지만 약간 번거로울 수 있다. 그렇기 때문에 대부분의 편의점 점주는 알바X에 맡기는 식으로 채용을 진행한다. 마찬가지로 C#에도 스레드를 일일히 선언하는 것보다 스레드 풀이란 곳에서 하나 가져오는 식으로 하는 편이 더 좋지 않을까 싶다.

 

ThreadPool의 사용법은 유틸 클래스를 사용하는 느낌에 더 가까운 편입니다. QueueUserWorkItem라는 것을 통해서 메서드를 콜백으로 등록을 해야 합니다.

public class ThreadExample
{
    public static void ThreadProc(object? o)
    {
        while (true)
            Console.WriteLine("I want to go home!");
    }

    public static void Main()
    {
        var b = ThreadPool.QueueUserWorkItem(callBack: ThreadProc);
    }
}

이것을 바로 실행하면 프로그램이 바로 끝나는 것을 확인할 수 있다. 즉 ThreadPool을 이용하면 백그라운드에서 실행한다는 점을 확인할 수 있다. 이런 기법을 스레드 풀링이라고 한다. 

 

다만 이 스레드 풀의 경우에는 너무 많은 일감을 한 번에 던지거나, 너무 한 번에 할 일이 많은 것들을 던져주면, 스레드 풀이 만들 수 있는 스레드의 양을 넘어가는 경우가 생겨버린다. 즉 알바X에서 더 이상 채용할 사람이 없는 것과 같은 것이다. (그렇다고 알바X의 개발자 분들을 데려다가 알바를 시킬 순 없으니....)