[게임 서버] 1.6 임계 영역과 뮤텍스

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
게임 서버 프로그래밍 교과서, 배현직 저자

📦 1. 멀티스레딩

👉🏻 6. 임계 영역과 뮤텍스

멀티스레드 환경에서는 경쟁 상태가 발생할 수 있다. 이걸 해결하기 위한 방법 중 하나는 다음과 같다:

다른 스레드가 X를 건드리려고 하면 기다린다.

이걸 위해 사용하는 것이 바로 뮤텍스(Mutex), 즉 상호 배제(Mutual Exclusion) 이다. 임계 영역(Critical Section) 이라고도 한다.


✅ 뮤텍스 사용법

  1. X, Y를 보호하는 뮤텍스 mx를 만든다.
  2. 스레드는 X, Y를 건드리기 전에 mx.lock()으로 잠근다.
  3. 데이터를 액세스한다.
  4. 끝나면 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는 메모리에 접근할 때 시간이 오래 걸림
  • 여러 스레드가 같은 데이터를 캐시에 접근 → 충돌 발생 → 성능 저하

🪓 뮤텍스를 잘게 나누면?

오히려 문제를 일으킨다.

  1. 락/언락 자체가 무거움 → 성능 저하
  2. 락 관리 복잡 → 교착 상태(deadlock) 발생 가능성 증가

예시

class Player {
	CriticalSection m_positionCritSec;
	CriticalSection m_nameCritSec;
	CriticalSection m_hpCritSec;
}

→ 락 순서를 잘못 지정하면 교착 상태 발생


🧠 결론

  • 너무 크게 나누면 → 싱글 스레드처럼 됨
  • 너무 잘게 나누면 → 복잡도 증가 + 위험

따라서 “적절히 잘게 나누는 것이 핵심”이다. 이에 대한 기준은 다음 챕터에서 다룬다.

GameServer 카테고리 내 다른 글 보러가기

댓글남기기