멀티스레드 개론

댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요.

댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요.

이 문서는 게임 서버를 공부하면서 프로세스, 스레드, CPU 코어, 스케줄링, 멀티코어, 컨텍스트 스위칭, 메모리 공유, 동시성 문제를 식당 비유로 정리한 학습 노트입니다.
먼저 전체 구조를 하나의 식당 세계관으로 잡습니다.
| 식당 비유 | 실제 컴퓨터 개념 |
|---|---|
| 레스토랑 | 프로세스(Process) |
| 그림판 식당 | 그림판 프로세스 |
| 메모장 식당 | 메모장 프로세스 |
| MMO 서버 식당 | MMO 서버 프로세스 |
| 로봇 직원 | 스레드(Thread) |
| 영혼 | CPU Core |
| 식당관리자 영역 | 운영체제, 특히 Windows Kernel |
| 로봇에게 영혼을 배정하는 관리자 | Scheduler |
| 영혼이 로봇을 갈아탐 | Context Switching |
프로세스(Process) 는 실행 중인 프로그램의 독립적인 실행 환경이라고 볼 수 있습니다.
예를 들어:
프로세스 내부에는 실제 작업을 수행하는 스레드(Thread) 가 존재합니다.
현재 학습을 위한 설정은 다음과 같습니다.
그림판 프로세스
└─ Thread 1
→ 싱글스레드
메모장 프로세스
└─ Thread 1
→ 싱글스레드
MMO 서버 프로세스
├─ Thread 1
└─ Thread 2
→ 멀티스레드
즉,
라고 이해할 수 있습니다.
스레드는 코드상 존재하는 실행 흐름이고, 실제 명령어를 실행하는 하드웨어는 CPU 코어입니다.
식당 비유에서는:
로봇 = Thread
로봇을 실제로 움직이게 하는 영혼 = CPU Core
라고 생각합니다.
현재 학습 모델에서는 CPU 코어 하나는 한 순간에 하나의 스레드만 실행한다고 가정합니다.
운영체제는 여러 프로세스와 스레드를 관리합니다.
Windows를 기준으로 보면, 커널 내부의 스케줄러(Scheduler) 가 어떤 스레드를 CPU에서 실행시킬지 결정합니다.
식당 비유로는 다음과 같습니다.
식당관리자(Windows Kernel)가
여러 레스토랑의 로봇(Thread)을 살펴보고
어떤 로봇에게 영혼(CPU Core)을 붙여줄지 결정한다.
이 과정을 스케줄링(Scheduling) 이라고 합니다.
초보자 설명에서는 흔히 다음과 같이 말할 수 있습니다.
"운영체제가 다음에 어떤 프로그램을 실행할지 결정한다."
하지만 더 정확하게는:
운영체제 커널이 실행 가능한 스레드들 중에서 어떤 스레드를 CPU에서 실행할지 결정한다.
라고 표현하는 것이 좋습니다.
스레드는 항상 CPU에서 실행 중인 것이 아닙니다.
대표적으로 다음과 같은 상태를 생각할 수 있습니다.
예를 들어 DB 응답을 기다리는 스레드는 우선순위가 높더라도 당장 CPU에서 실행할 수 없습니다.
스레드마다 우선순위를 다르게 설정할 수 있습니다.
예:
Thread A : 우선순위 높음
Thread B : 우선순위 보통
Thread C : 우선순위 낮음
여러 스레드가 동시에 Ready 상태라면 스케줄러는 우선순위를 고려하여 실행할 스레드를 선택합니다.
다만 실제 Windows의 스케줄링은 단순히 "높은 숫자부터 영원히 실행"하는 구조는 아니며, 동적 우선순위 조정 등의 보정도 사용합니다.
낮은 우선순위의 스레드가 실행할 준비가 되어 있음에도 계속 다른 스레드에 밀려 CPU 실행 기회를 얻지 못할 수 있습니다.
이런 현상을 기아(Starvation) 라고 합니다.
정확한 표현은 다음과 같습니다.
스레드가 Ready 상태임에도 다른 작업이 계속 우선적으로 선택되어 오랫동안 CPU 실행 시간을 얻지 못하는 현상
식당 비유로는:
로봇은 일할 준비가 되어 있는데
식당관리자가 계속 다른 로봇에게만 영혼을 배정해서
자기 차례가 오지 않는 상황입니다.
CPU 성능을 높이기 위해 단순히 하나의 코어의 클럭만 계속 높이면 전력 소모와 발열 문제가 커집니다.
그래서 현대 CPU는 하나의 CPU 안에 여러 개의 물리 코어를 배치하는 방향으로 발전했습니다.
이러한 구조를 멀티코어(Multi-core) 라고 합니다.
식당 비유에서는:
영혼이 1개가 아니라 여러 개가 된 상태
라고 볼 수 있습니다.
예를 들어 4코어 CPU라면:
Core 1 → Thread A
Core 2 → Thread B
Core 3 → Thread C
Core 4 → Thread D
처럼 여러 스레드를 실제로 동시에 실행할 수 있습니다.
이것을 병렬 실행(Parallelism) 이라고 합니다.
아닙니다.
스레드를 많이 만든다고 해서 성능이 무조건 좋아지는 것은 아닙니다.
CPU 코어 수보다 실행하려는 스레드가 지나치게 많으면 여러 스레드가 같은 CPU 코어를 두고 경쟁하게 됩니다.
예를 들어 4코어 CPU에서 CPU 작업을 계속 수행하는 스레드가 100개라면:
Core 1 : Thread 1 → Thread 5 → Thread 9 → ...
Core 2 : Thread 2 → Thread 6 → Thread 10 → ...
Core 3 : Thread 3 → Thread 7 → Thread 11 → ...
Core 4 : Thread 4 → Thread 8 → Thread 12 → ...
처럼 코어들이 계속 실행 대상을 교체해야 합니다.
실행하려는 스레드 수가 처리 가능한 코어 수에 비해 지나치게 많은 상태를 오버서브스크립션(Oversubscription) 이라고 부를 수 있습니다.
CPU 코어가 현재 실행 중인 스레드를 멈추고 다른 스레드를 실행하기 시작하는 과정을 컨텍스트 스위칭(Context Switching) 이라고 합니다.
식당 비유에서는:
영혼이 로봇 A에서 빠져나와 로봇 B에게 빙의하는 과정
입니다.
대략적인 흐름은 다음과 같습니다.
Thread A 실행
↓
Thread A의 실행 상태 저장
↓
Thread B의 실행 상태 복원
↓
Thread B 실행
이 과정은 공짜가 아닙니다.
운영체제는 기존 스레드의 실행 상태를 저장하고, 새로운 스레드의 실행 상태를 복구해야 합니다.
또한 스레드가 자주 바뀌면 CPU 캐시 효율이 떨어져 Cache Miss가 증가할 수도 있습니다.
스레드가 지나치게 많아지면 다음과 같은 문제가 생길 수 있습니다.
스레드 수 증가
↓
CPU를 차지하려는 경쟁 증가
↓
스케줄링 증가
↓
Context Switching 증가
↓
CPU Cache 효율 저하 가능
↓
실제 작업 외의 관리 비용 증가
↓
성능 저하 가능
따라서:
멀티스레드는 스레드 수 자체가 중요한 것이 아니라, 적절한 수의 스레드를 어떤 역할로 어떻게 배치하느냐가 중요합니다.
같은 프로세스에 속한 여러 스레드는 같은 주소 공간을 사용합니다.
프로세스의 메모리를 단순화하면 다음처럼 생각할 수 있습니다.
MMO Server Process
공유 영역
├─ Code
├─ Data / BSS
└─ Heap
Thread A
└─ Stack A
Thread B
└─ Stack B
같은 프로세스 안의 스레드들은 일반적으로 다음 영역을 공유합니다.
예를 들어 전역 변수나 Heap에 생성된 객체를 여러 스레드가 함께 접근할 수 있습니다.
각 스레드는 자신의 Stack 을 따로 가집니다.
Thread A → Stack A
Thread B → Stack B
멀티스레드 프로그래밍이 어려운 가장 큰 이유 중 하나는 여러 스레드가 공유 데이터(Shared Data) 에 접근할 수 있다는 점입니다.
예를 들어 MMO 서버의 두 스레드가 같은 플레이어의 골드 값을 수정한다고 가정해보겠습니다.
Player Gold = 100
Thread A → Gold + 10
Thread B → Gold - 20
두 스레드의 실행 순서가 예상과 다르게 섞이면 결과가 잘못될 수 있습니다.
여러 스레드의 실행 순서에 따라 프로그램의 결과가 달라지는 상황을 Race Condition(경쟁 상태) 라고 합니다.
여러 스레드가 동기화 없이 같은 메모리에 접근하고, 그중 하나 이상이 해당 데이터를 수정하는 상황을 Data Race라고 합니다.
여러 스레드가 동시에 실행하면 문제가 생길 수 있는 코드 영역을 Critical Section(임계 영역) 이라고 부릅니다.
예:
Player Gold 수정
Inventory 수정
공유 Queue 수정
공유 Map 수정
등이 임계 영역이 될 수 있습니다.
여러 스레드가 공유 자원을 안전하게 사용할 수 있도록 실행 순서를 조정하는 것을 동기화(Synchronization) 라고 합니다.
대표적인 도구는 다음과 같습니다.
공유 자원에 한 스레드가 접근 중이라면 다른 스레드가 동시에 접근하지 못하도록 막는 방식입니다.
식당 비유에서는:
공용 창고에 한 번에 로봇 한 명만 들어갈 수 있도록 열쇠를 하나 두는 것
과 비슷합니다.
여러 스레드가 동일한 Lock을 동시에 얻으려고 경쟁하는 상황을 Lock Contention 이라고 합니다.
스레드는 여러 개지만 같은 Lock을 기다려야 한다면 실제로는 다음과 같이 동작할 수 있습니다.
Thread A ─┐
Thread B ─┼─→ 같은 Lock을 기다림
Thread C ─┘
이 경우 멀티스레드의 병렬 처리 장점이 줄어듭니다.
여러 스레드가 서로 상대방이 가지고 있는 자원을 기다리며 아무도 진행하지 못하는 상태를 Deadlock(교착 상태) 이라고 합니다.
예:
Thread A
- Lock 1 보유
- Lock 2를 기다림
Thread B
- Lock 2 보유
- Lock 1을 기다림
그러면 두 스레드 모두 영원히 기다릴 수 있습니다.
식당 비유로 다시 압축하면:
레스토랑
= Process
로봇
= Thread
영혼
= CPU Core
식당관리자
= Windows Kernel
어느 로봇에게 영혼을 줄지 결정
= Scheduling
영혼이 다른 로봇으로 이동
= Context Switching
로봇들이 함께 사용하는 창고
= Shared Memory / Heap / Data
여러 로봇이 같은 물건을 동시에 수정
= Race Condition / Data Race
공용 창고 열쇠
= Lock / Mutex
여러 로봇이 같은 열쇠를 기다림
= Lock Contention
서로 상대방의 열쇠를 기다림
= Deadlock
계속 다른 로봇에게 밀려 영혼을 받지 못함
= Starvation
멀티스레딩의 목적은 스레드를 많이 만드는 것이 아니라, 여러 CPU 코어를 효율적으로 활용하면서 공유 데이터에 대한 접근을 안전하게 설계하는 것입니다.
특히 게임 서버에서는 다음 질문이 중요합니다.
어떤 스레드가 어떤 작업을 담당하고, 어떤 데이터를 소유하며, 어떤 데이터를 다른 스레드와 공유할 것인가?
이 구조가 명확할수록 멀티스레드 서버를 더 안전하고 효율적으로 설계할 수 있습니다.

우리들의 게임 발매 이야기

안녕하세요. 플밍 4기 입니다. 게임 개발을 배우기 전 네트워크 엔지니어 도메인에서 익히고 배웠던 네트워크 이론에 대한 기초 입니다. 학습에 도움이 되길 바라며 공유 드립니다.
XR을 활용한 게임 개발 3기(유니티) 수강생입니다. 곧 수료 하지만 앞으로 이곳에 가끔 저의 개발 경험이 나 지식 기록할까 합니다. 더 나아가 이 사이트가 제 개인위키의 역할을 할 수 있으면 좋겠습니다. 한국 게임 시장을 흔들겠습니다


안녕하세요. 플밍 4기 입니다. 게임 개발을 배우기 전 네트워크 엔지니어 도메인에서 익히고 배웠던 네트워크 이론에 대한 기초 입니다. 학습에 도움이 되길 바라며 공유 드립니다.
XR을 활용한 게임 개발 3기(유니티) 수강생입니다. 곧 수료 하지만 앞으로 이곳에 가끔 저의 개발 경험이 나 지식 기록할까 합니다. 더 나아가 이 사이트가 제 개인위키의 역할을 할 수 있으면 좋겠습니다. 한국 게임 시장을 흔들겠습니다
