[게임 서버] 1.7 교착 상태

업데이트:     Updated:

카테고리:

태그:

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

📦 1. 멀티스레딩

👉🏻 교착 상태

int a;
int b;
// 스레드 1
Thread() {
	lock(a) {        // 1
		a++;
		lock(b) {    // 3
			b++;
		}
	}
}
// 스레드 2
Thread() {
	lock(b) {        // 2
		b++;
		lock(a) {    // 4
			a++;
		}
	}
}

두 스레드를 번갈아 실행하다가 1 → 2 → 3 → 4 순으로 실행되면,
스레드 1은 b의 잠금을 기다리고, 스레드 2는 a의 잠금을 기다리는 상태가 된다.

→ 서로 상대가 락을 풀기만을 기다리는 상태, 즉 교착 상태(deadlock)


1️⃣ 교착 상태가 되면?

  • CPU 사용량은 0%에 가까움
  • 클라이언트 요청에 응답 없음
  • 동시에 접속한 사용자가 많든 적든 무조건 멈춘다
  • 서버는 켜져있지만 동작은 안 한다

2️⃣ 원인 찾기

  • Windows에서 제공하는 CRITICAL_SECTION은 내부 상태를 확인할 수 있다
  • 디버거에서 해당 객체 정보를 보면, → 어떤 스레드가 어디서 멈췄는지 추적 가능

3️⃣ CRITICAL_SECTION 클래스 구현

class CriticalSection {
	CRITICAL_SECTION m_critSec;
public:
	CriticalSection();
	~CriticalSection();
	void Lock();
	void Unlock();
};

class CriticalSectionLock {
	CriticalSection* m_pCritSec;
public:
	CriticalSectionLock(CriticalSection& critSec);
	~CriticalSectionLock();
};
CriticalSection::CriticalSection() {
	InitializeCriticalSectionEx(&m_critSec, 0, 0);
}

CriticalSection::~CriticalSection() {
	DeleteCriticalSection(&m_critSec);
}

void CriticalSection::Lock() {
	EnterCriticalSection(&m_critSec);
}

void CriticalSection::Unlock() {
	LeaveCriticalSection(&m_critSec);
}
CriticalSectionLock::CriticalSectionLock(CriticalSection& critSec) {
	m_pCritSec = &critSec;
	m_pCritSec->Lock();
}

CriticalSectionLock::~CriticalSectionLock() {
	m_pCritSec->Unlock();
}

🔹 핵심 요약

  • 생성: InitializeCriticalSectionEx
  • 삭제: DeleteCriticalSection
  • 잠금: EnterCriticalSection
  • 해제: LeaveCriticalSection
  • CriticalSectionLock 클래스는 RAII 방식으로 자동 잠금/해제를 보장


RAII 방식에 대해선 [C++] 항목 13: 자원 관리에는 객체가 그만! 에서 알아볼 수 있다.


💡 교착 상태를 재현하는 코드 예제

int a;
CriticalSection a_mutex;
int b;
CriticalSection b_mutex;

int main() {
	thread t1([]() {
		while (1) {
			CriticalSectionLock lock(a_mutex);
			a++;
			CriticalSectionLock lock2(b_mutex);
			b++;
			cout << "t1 done.\n";
		}
	});

	thread t2([]() {
		while (1) {
			CriticalSectionLock lock(b_mutex);
			b++;
			CriticalSectionLock lock2(a_mutex);
			a++;
			cout << "t2 done.\n";
		}
	});

	t1.join();
	t2.join();
	return 0;
}

🔹 실행 흐름:

  • t1은 a → b 순으로 락
  • t2는 b → a 순으로 락
  • 둘이 동시에 락을 걸고 있다가 서로가 가진 락을 기다리는 상황이 오면 교착 상태


출력 예시:

t1 done.
t1 done.
t1 done.
...
(이후 멈춤)

→ 프로그램은 죽지 않지만, 진행이 멈춘다


⚠️ 이번 정리에선 디버거 실습은 생략 대신 교착 상태가 어떻게 생기는지, 코드와 함께 원리 위주로 설명


🧐 정리

  • 교착 상태는 서로가 필요한 락을 이미 상대가 가지고 있을 때 발생
  • 이 상태에선 프로그램이 응답을 멈추고, CPU 사용량도 거의 없다
  • RAII 패턴을 이용해 락 해제를 자동화하면 실수 줄일 수 있다

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

댓글남기기