[게임 서버] 1.6 임계 영역과 뮤텍스
카테고리: GameServer
태그: GameServer
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
게임 서버 프로그래밍 교과서, 배현직 저자
📦 1. 멀티스레딩
👉🏻 6. 임계 영역과 뮤텍스
멀티스레드 환경에서는 경쟁 상태가 발생할 수 있다. 이걸 해결하기 위한 방법 중 하나는 다음과 같다:
다른 스레드가 X를 건드리려고 하면 기다린다.
이걸 위해 사용하는 것이 바로 뮤텍스(Mutex), 즉 상호 배제(Mutual Exclusion) 이다. 임계 영역(Critical Section) 이라고도 한다.
✅ 뮤텍스 사용법
- X, Y를 보호하는 뮤텍스
mx
를 만든다. - 스레드는 X, Y를 건드리기 전에
mx.lock()
으로 잠근다. - 데이터를 액세스한다.
- 끝나면
mx.unlock()
으로 잠금 해제한다.
mutex mx;
mx.lock();
read(x);
write(y);
sum(x);
mx.unlock();
🔄 스레드 타이밍 예시
스레드/시간 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
스레드 A | 2 | 3 | 3 | 4 | X | X |
스레드 B | X | 2 | 2 | 2 | 3 | 4 |
뮤텍스를 잠글 땐 lock()
,
해제할 땐 unlock()
을 사용한다.
❗예외 처리 문제
mx.lock();
read(x); // 예외 발생 가능
write(y);
sum(x);
mx.unlock(); // 실행되지 않음
→ 예외 발생 시 unlock()
이 호출되지 않음
→ 다른 스레드가 접근 불가 → 무한 대기 상태 발생
✅ 해결법: lock_guard
사용
recursive_mutex mx;
lock_guard<recursive_mutex> lock(mx);
read(x);
write(y);
sum(x); // 블록을 벗어나면 자동 unlock
왜 recursive_mutex
?
- 기본
mutex
는 같은 스레드가 중복 잠금 불가 recursive_mutex
는 같은 스레드가 여러 번 잠금 가능
✅ C#에서의 사용 예시
object mx = new object();
lock(mx) {
read(x);
write(y);
sum(x);
}
→ C#의 lock
도 블록을 벗어나면 자동 unlock
✅ C++에서도 똑같이 가능
mutex mx;
{
lock_guard<mutex> lock(mx);
read(x);
write(y);
sum(x);
}
🧪 예제: 뮤텍스 적용한 소수 구하기
num
, primes
를 보호하는 recursive_mutex
를 사용하여 다음과 같이 수정:
핵심 코드 흐름
int num = 1;
recursive_mutex num_mutex;
vector<int> primes;
recursive_mutex primes_mutex;
스레드 내부
while (true)
{
int n;
{
lock_guard<recursive_mutex> num_lock(num_mutex);
n = num;
num++;
}
if (n >= MaxCount) break;
if (IsPrimeNumber(n))
{
lock_guard<recursive_mutex> primes_lock(primes_mutex);
primes.push_back(n);
}
}
결과
Took 1358 milliseconds.
- 1스레드: 약 3900ms
- 4스레드: 약 1300ms → 3배 향상 (4배 아님)
📉 왜 4배가 아니었을까?
1. 스레드가 항상 Runnable은 아니다
lock_guard
사용 중 다른 스레드는 대기하게 됨
2. 메모리 접근이 느림 (메모리 바운드)
- CPU는 메모리에 접근할 때 시간이 오래 걸림
- 여러 스레드가 같은 데이터를 캐시에 접근 → 충돌 발생 → 성능 저하
🪓 뮤텍스를 잘게 나누면?
오히려 문제를 일으킨다.
- 락/언락 자체가 무거움 → 성능 저하
- 락 관리 복잡 → 교착 상태(deadlock) 발생 가능성 증가
예시
class Player {
CriticalSection m_positionCritSec;
CriticalSection m_nameCritSec;
CriticalSection m_hpCritSec;
}
→ 락 순서를 잘못 지정하면 교착 상태 발생
🧠 결론
- 너무 크게 나누면 → 싱글 스레드처럼 됨
- 너무 잘게 나누면 → 복잡도 증가 + 위험
따라서 “적절히 잘게 나누는 것이 핵심”이다. 이에 대한 기준은 다음 챕터에서 다룬다.
댓글남기기