스레드는 프로그램(프로세스)의 작업을 실제로 실행하는 단위이다.
기본적으로 스레드는 프로그램당 한 개씩가지고있지만 필요에따라서 여러개를 만들어 줄 수있다.
만약 코어가 하나인 컴퓨터에서도 여러개의 프로그램을 실행 할 수있는 이유는 매우 빠른속도로 코어가 스레드를 처리하고있기때문에 동시에 처리하는 것처럼 보이는 것이다.
하지만 한 스레드에서 다른 스레드로 옮기는 작업이 꽤나 무거운 작업이기 때문에 이상적인 상황은 한 코어에 하나의 스레드가 대응되고있는 상황이다.
C#에서 스레드를 직접적으로 확인하기 위해서 코드를 짜본다면
using System;
using System.Threading;
namespace ServerCore
{
internal class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 4; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
Thread t = new Thread(MainThread); //스레드한테 할일을 부여
t.Start();
Console.WriteLine("Hello World!");
}
}
}
스레드에게 Hello Thread!를 4번 출력하는 일을 맡기고 메인에서도 Hello World!를 출력하는 임무를 수행한다.

정상적으로 잘 출력된다. 여기서 이해하고 있어야할 점은 한 개의 코어인 경우 메인 스레드와 추가로 생성한 스레드를 빠르게 반복하면서 실행했을 점이라는거다.
기본적으로 만들어준 스레드는 foreground 스레드로 실행된다고 한다. 간단하게 말해서 메인스레드가 할 일을다해도 다른 스레드가 끝나지 않으면 메인스레드가 끝나지 않는다는거다.
backgroud 스레드이면 반대로 메인스레드가 끝나면 다른 스레드는 그냥 무시하고 끝내버린다.
using System;
using System.Threading;
namespace ServerCore
{
internal class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 100; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
Thread t = new Thread(MainThread); //스레드한테 할일을 부여
t.Start();
t.IsBackground = true;
Console.WriteLine("Hello World!");
}
}
}
차이를 명확하게 보이게하기위해 background로 설정하고 다른 스레드에 100번 실행하게 만들고 실행해보면

100번 실행되지않았지만 메인 스레드가 끝나버려서 그대로 종료해버렸다.
당연히 false로하면 100번 모두 실행하고 종료시킨다.
다른 스레드가 끝날때 까지 기다린다음 실행시키는 join기능도 존재한다.
using System;
using System.Threading;
namespace ServerCore
{
internal class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 10; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
Thread t = new Thread(MainThread); //스레드한테 할일을 부여
t.Start();
t.Join();
Console.WriteLine("Hello World!");
}
}
}
이렇게 설정하면 10번의 Hello Thread를 출력하고나서야 HelloWorld를 출력한다.
하지만 이렇게 new를때려서 스레드를 직접만드는 작업은 무거운 작업 이므로 C#에서 만들어진 ThreadPool을 주로 사용한다.
using System;
using System.Threading;
namespace ServerCore
{
internal class Program
{
static void MainThread(object state)
{
for(int i = 0; i < 4; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(MainThread);
}
}
}
위 코드로 실행하게되면 아무것도 출력되지 않는다. 그이유는 ThreadPool는 기본 설정이 background이기 때문에 실행되기도 전에 메인 스레드가 끝나버려서 출력도 못하고 종료된것이다.
그렇다면 메인 스레드를 무한루프에 가두고 출력해보자
using System;
using System.Threading;
namespace ServerCore
{
internal class Program
{
static void MainThread(object state)
{
for(int i = 0; i < 4; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(MainThread);
while(true)
{
}
}
}
}

정상적으로 잘 출력되는 모습이다.
ThreadPool에 대기하고있는 스레드 수를 설정하는것도 가능하다.
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(5, 5);
하지만 ThreadPool를 통해 스레드를 작업시킬 때의 문제점은 ThreadPool에있는 모든 스레드를 사용해버려서 더 이상 작업해줄 스레드가 남지 않을 상황을 상상해봐야한다.
이러한 상황은 프로그래머의 예상을 뛰어넘는 작업시간을 가지게된다면 발생가능하다.
이것을 개선하기 위해 Task키워드에서 TaskCreationOptions.LongRunning라는 옵션을 제공한다.
Task는 기본적으로 ThreadPool에서 관리된다. 즉, 스레드에서 작업가능한 스레드가 있으면 작업을 시킨다.
using System;
using System.Threading;
namespace ServerCore
{
internal class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 4; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(5, 5);
for (int i = 0; i < 5; i++)
{
Task t = new Task(() => { while (true) { } });
t.Start();
}
ThreadPool.QueueUserWorkItem(MainThread);
while (true) { }
}
}
}
위 코드를 실행해도 아무것도 나오지 않는다. 아무런 옵션을 넣어주지 않아서 Task는 스레드 풀에있는 스레드를 통해 작업한다. 그러므로 이용가능한 스레드가 없으므로 아무것도 출력할수가없는것이다.
이제 Task에서 TaskCreationOptions.LongRunning옵션을 넣어보자, 이 옵션을 넣어준다는말은 "이 작업은 오래걸리니까 스레드플의 스레드를 쓰지말고 독립적인 스레드를 만들어"라는 의미라고 생각하면 된다.
using System;
using System.Threading;
namespace ServerCore
{
internal class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 4; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(5, 5);
for (int i = 0; i < 5; i++)
{
Task t = new Task(() => { while (true) { } }, TaskCreationOptions.LongRunning);
t.Start();
}
ThreadPool.QueueUserWorkItem(MainThread);
while (true) { }
}
}
}
이제 5개의 독립적인 스레드를 만들어서 작업을 수행하고 스레드풀에는 여전이 5개의 스레드가 남아있다.

의도한대로 출력한 모습이다.
'서버 공부' 카테고리의 다른 글
| [서버 공부]6.Lock구현 - Context Switching (0) | 2025.01.03 |
|---|---|
| [서버 공부]5.Lock구현 - SpinLock (0) | 2025.01.02 |
| [서버 공부]4. Lock (0) | 2025.01.02 |
| [서버 공부]3.메모리 배리어 + Interlocked (0) | 2025.01.01 |
| [서버 공부]2.컴파일 최적화 + 캐시 (0) | 2025.01.01 |