본문 바로가기
CS

[CS] 공유 자원의 늪: Race Condition과 교착상태

by 비니공일 2026. 5. 28.

단일 스레드와 멀티 스레드

  • 단일 스레드

단일 스레드는 하나의 작업이 끝나면 다음 작업이 시작된다. 따라서 다른 작업이 기다리는 시간이 길어지고 작업을 처리하는 총 시간도 같이 길어지는 문제가 있다. node.js와 javascript가 단일 스레드 형태로 되어 있다.

  • 멀티 스레드

반면 멀티 스레드는 작업이 동시/병렬적으로 처리된다. Java 서버, 웹 브라우저 렌더링이 이에 해당된다.

멀티 스레드 환경에서 자원 공유 시 발생하는 문제점

1. 경쟁 상태(Race Condition)

경쟁상태란, 하나의 프로세스 안에서 여러 스레드가 공통된 자원에 접근하면서 동시에 읽고 수정이 벌어져 예기치 않은 결과가 나오는 현상이다.

만약에 통장 잔고가 5000원이 남아있고, 두 스레드가 동시에 1000원씩 가져간다고 가정해보자.

① 읽기  (READ)  : temp = balance      // 메모리에서 값을 읽어 레지스터에 저장
② 계산  (CALC)  : temp = temp - 1000   // 레지스터 안에서 빼기
③ 쓰기  (WRITE) : balance = temp      // 계산 결과를 메모리에 다시 저장

첫 번째 사람이 ①, ② 를 진행하고 ③ 전에 두 번째 사람이 접근하기 때문에 1000원 빼는 동작을 동시에 한다.

즉 예상 잔고는 두 스레드가 1000원씩 가져갔으니 5000-2000 = 3000원이 남아야하지만, 결과는 4000이 된다는 것이다.

 

 

2. 교착 상태(Deadlock)

두 스레드가 서로 가진 자원을 기다리며 무한 대기 상태에 빠지는 현상을 말한다. 마치 두 사람이 한 길에서 마주쳐서 서로 비켜주기를 기다리다가 계속 못 지나가는 현상과 같다.

 

‘데드락의 4대조건’

교착상태는 아래 4가지 조건일때 발생한다. 하나라도 어긋나면 교착상태는 발생하지 않는다.

  • 상호배제(Mutual exclusion): 자원은 한 번에 한 스레드만 사용 가능함
  • 점유와 대기(Hold and Wait): 자원을 가진 채로 다른 자원을 기다림
  • 비선점(No preemption): 다른 스레드가 가지고 있는 자원을 강제로 뺏을 수 없음
  • 환형 대기(Circular wait): A→B→A 식으로 대기가 원형으로 연결됨

[해결방법]

  • 예방: 데드락의 4대 조건 중에 하나가 발생하지 않도록 하는 것. 예) 환형 대기 허용 → 모든 자원에 순서를 부여한다.
  • 회피: 교착상태가 발생할 가능성이 있다면 적절히 조심하면서 피해가는 것. 교착상태의 가능성을 염두해 둔 채 행동하는 것.
  • 탐지 및 회복: 데드락 발생을 허용하되, 발생한다면 해결하는 것.
  • 무시: 데드락 발생 가능성이 높지만 해결 비용이 많이 든다면, 해결하지 않고 프로그램을 재부팅하도록 냅두는 것처럼 해결을 무시하는 것. 교착상태의 가능성을 염두해두지 않는 것

3. 기아 상태(Starvation)

특정 프로세스의 우선 순위가 낮아서 원하는 자원을 계속 할당받지 못하는 상태

<교착상태와 기아상태 차이>
교착상태는 ‘여러 스레드가 동일한 자원을 얻지 못한채 계속 기다려서 시스템 전체 먹통이 되는 것’이고,
기아상태는 ‘다른 스레드는 잘 돌아가는데 특정 스레드만 계속 소외된 채 자원 할당이 안되는 상황’ 인것.

 

[해결방법]

  • 에이징: 스레드가 오래 기다릴수록 우선순위를 높여주는 것.

4. 메모리 가시성 문제

한 스레드가 공유 변수의 값을 변경했는데, 다른 스레드는 그 변경된 값이 보이지 않는 데이터 불일치 문제

 

상호배제 기법

멀티 스레드 환경에서 발생하는 문제를 해결하는 방법은 상호배제 기법이다.

상호배제는 교착 상태 조건 중 하나로, 임계 영역에 두 개의 프로세스가 접근하지 못하도록 하는 것이다.

  • 임계영역: 두 개의 프로세스가 동시 접근할 수 없는 자원
  • 임계자원: 그 자원을 접근하는 프로그램 코드 일부분

 

뮤텍스(Mutex: Mutual Exclusion)

상호배제의 줄임말로, 오직 1개의 스레드만이 임계영역에 접근할 수 있도록 Lock을 걸어놓는 것이다.

뮤텍스의 작동 방식은 화장실과 화장실 열쇠가 하나뿐인 식당과 같다.

  1. 식당(임계영역)에는 화장실 한 칸과 화장실 열쇠 한 개가 있다.
  2. A(프로세스)가 열쇠(Lock)를 가지고 화장실에 간다.
  3. 화장실에 가려던 B(프로세스)는 열쇠가 없어서 기다린다.
  4. A가 화장실에서 나와 키를 반납하면, B가 그 키를 가지고 화장실에 간다.

한 프로세스가 임계영역에 락을 걸면, 해제할때까지 다른 프로세스는 대기한다는 방식이라고 해서 ‘락킹 매커니즘’이라고도 한다.

임계 영역에 접근하지 못한 프로세스는 락을 얻기 위해 대기하는 동안, 락이 풀렸는지 확인하는데,

이를 ‘스핀락’이라고 한다.

 

 

세마포어

뮤텍스과 비슷한 매커니즘이지만, 세마포어는 공유 자원이 1개 이상일 경우이다.

임계영역에 접근할 수 있는 키 N개를 지정하고, 이 중 하나를 가진 프로세스만이 임계영역에 접근하도록 하는 것이다. 프로세스가 접근을 해제할 시 다른 프로세스에게 접근할 수 있도록 신호를 보낸다고 해서 ‘시그널링 매커니즘’이라고도 불린다.