멀티 스레드에 대한 설명은 사실 예전에 C++로 설명하기 전에 간단하게 설명한 포스트가 있습니다. 이것을 간단하게 참조해주시면 되겠습니다. https://frokdevelop.tistory.com/43
위의 글을 간단하게 설명하자면 결국 컴퓨터가 한 번에 많은 일을 동시에 처리할 수 있게, 스레드라고 부르는 작은 프로세스에 일감을 나눠주는 식을 말하는 겁니다.
C#은 다행히도 최근 전까지만 해도(사실 좀 되긴 했지만) 그 OS에서 제공하는 API를 빠삭하게 알아야 했던 C++에 비해서는 굉장히 스레드 관리가 쉬운 편입니다. 나름 자바를 따라가려 했었고 이후로도 지속적인 개량이 이루어진 그런 언어이기 때문에 이런 개념에 대해서 충분히 쉽게 쓸 수 있고, 지금 와서는 저는 조심스럽게 자바보다도 이런 면에서도 그렇고 더 현대적인 언어라고 생각합니다. (주관적인 생각입니다!) - 참고 : https://learn.microsoft.com/ko-kr/dotnet/csharp/whats-new/csharp-version-history
서론이 길었습니다. 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라는 직원을 고용하고, 이 직원에게 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나 그 외 정보들을 파악할 수 있게 됩니다.
지금까지 한 것은 내가 일일히 직원을 고용하는 식이다. 하지만 생각을 해보자. 내가 편의점 주인이고 알바를 구하는데, 편의점 앞에 알바 공고를 붙여도 물론 고용은 되겠지만 약간 번거로울 수 있다. 그렇기 때문에 대부분의 편의점 점주는 알바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의 개발자 분들을 데려다가 알바를 시킬 순 없으니....)