[게임 서버] 1.5 스레드를 다룰 때 주의 사항
카테고리: GameServer
태그: GameServer
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
게임 서버 프로그래밍 교과서, 배현직 저자
📦 1. 멀티스레딩
👉🏻 5. 스레드를 다룰 때 주의 사항
⚠️ 경쟁 상태 (데이터 레이스)
두 스레드가 데이터에 접근해서 그 데이터 상태를 예측할 수 없게 되는 상황
예를 들어, 이런 코드가 있다고 해보자.
x += y;
이걸 기계어 수준으로 바꾸면 다음과 같이 된다.
t = x
t = t + y
x = t
자, 이걸 두 개의 스레드가 동시에 실행한다면?
✅ 이상적으로 돌아가는 상황
x = 2
# 스레드 1
t1 = x // t1 = 2
t1 = t1 + 3 // t1 = 5
x = t1 // x = 5
# 스레드 2
t2 = x // t2 = 5
t2 = t2 + 4 // t2 = 9
x = t2 // x = 9
이런 식으로 순차적으로 잘 돌아가면 괜찮다. 하지만 현실은 그렇지 않다.
❌ 실제로는 이렇게 될 수도 있다
x = 2
# 스레드 1
t1 = x // t1 = 2
t1 = t1 + 3 // t1 = 5
# 스레드 2 (도중에 컨텍스트 스위치 발생)
t2 = x // t2 = 2
t2 = t2 + 4 // t2 = 6
x = t2 // x = 6
# 다시 스레드 1
x = t1 // x = 5 ← ❗ 예상치 못한 값
결과적으로 x = 9
가 돼야 할 것 같았는데,
x = 5가 되어버렸다.
→ 바로 이게 데이터 레이스다.
🔍 이전의 소수 구하기 프로그램에서 무슨 문제가 있었을까?
int num = 1;
Array<int> primes;
ThreadProc() {
while(num <= 1000000) {
if(IsPrime(num))
primes.Add(num);
num++;
}
}
main() {
Array<Thread> threads;
for(int i=0; i<4; i++)
threads.Add(BeginThread(ThreadProc));
for(int i=0; i<4; i++) {
threads.WaifForExit(); // thread->join() 같은 역할
}
PrintNumber(primes);
}
이 코드는 겉보기엔 문제 없어보이지만, 치명적인 문제 두 가지가 숨어있다.
🧨 문제 1. 여러 스레드가 num
에 동시에 접근
- 여러 스레드가 동시에
num
을 읽는다. num
을 동시에 증가시킨다.
이건 기계어 수준으로 보면 이렇게 된다.
r1 = num
r1 = r1 + 1
num = r1
즉, 같은 값을 여러 번 처리하거나, 값을 건너뛰는 일이 생길 수 있다.
🧨 문제 2. 여러 스레드가 primes
에 동시에 접근
Array<int> primes
에는 내부적으로
- 배열을 가리키는 포인터
- 배열의 크기 정보
이 두 가지 중요한 멤버가 있다.
프라임 숫자를 추가할 때 공간이 부족하면
→ 메모리를 재할당하게 된다.
그럼 포인터가 가리키는 위치가 바뀌게 되는데,
다른 스레드가 그걸 모른 채 접근하면?
→ 이미 해제된 메모리에 접근하게 되고
→ 충돌이 발생한다.
🧷 해결 방법: 동기화
이런 문제를 막기 위해 공유 데이터에 접근하는 순간만큼은 다른 스레드가 접근하지 못하도록 막아야 한다.
🧱 원자성 (Atomicity)
- 하나의 작업 단위는 절대 쪼개지지 않게 만든다.
num++
,primes.Add()
와 같은 연산은 → 반드시 하나의 원자적인 동작으로 보장해야 한다.
🔗 일관성 (Consistency)
- 여러 멤버 변수(
배열 포인터
,크기
)가 항상 서로 어울리는 상태여야 한다. - 재할당 중간에 끼어들지 않도록 보호해야 한다.
🔐 이걸 가능하게 하는 기술: 동기화(Synchronization)
- 임계 영역 (Critical Section)
- 뮤텍스 (Mutex)
- 잠금 (Lock)
이런 기법들을 사용하면 멀티스레드 환경에서도 안전하게 데이터를 다룰 수 있다.
즉, 멀티스레딩을 하다 보면 원자성과 일관성을 반드시 고려해야 하고, 이를 위해선 적절한 동기화 기법을 써야 한다.
댓글남기기