[CS 스터디] 임계구역과 동기화

Jun_k·2026년 7월 19일

CS

목록 보기
18/19

"왜 10만 원 + 10만 원 = 10만 원이 될까?"

  • 우리가 볼 때 "예금 = 예금 + 10만 원"은 하나의 단순한 행동 같지만
    컴퓨터(CPU) 입장에서는 이 작업이 3개의 아주 작은 단위로
    쪼개져서 실행되기 때문이다.

  • 읽기: 메모리에서 현재 예금(0원)을 CPU로 가져온다.

  • 계산: 가져온 0원에 10만 원을 더한다.

  • 쓰기: 계산된 10만 원을 다시 메모리에 저장한다.

[왜 문제가 터질까?]

  • 스레드 T1이 읽기와 계산까지만 하고 아직 쓰기를 하기 직전
    (메모리에는 여전히 0원인 상태)에

    스레드 T2가 읽기를 실행해 버린다.
    그러면 T2도 0원을 읽어 가고, 결국 두 스레드 모두 0원에 10만 원을 더한 뒤
    각각 '10만 원'을 메모리에 덮어쓰게 된다.
    이렇게 실행 순서에 따라 결과가 망가지는 것을 경쟁 상태(Race Condition)라고 한다.


"분명 잠금 코드를 짰는데, 왜 잠금이 뚫릴까?"

책을 보면 while(lock == true); 로 남이 사용 중인지 확인하고,
lock = true; 로 내가 잠금을 거는 코드가 나온다.
뭔가 완벽하게 잠금을 한 것 같지만 컴퓨터에서는 타임 슬라이스 아웃 때문에
문제가 발생할 수 있다.

타임 슬라이스 아웃

  • 하나의 CPU 코어는 여러 스레드를 아주 짧은 시간(Time Slice)씩 번갈아 가며 실행한다.주어진 시간이 다 되면 CPU는 얄짤없이 하던 일을 멈추고 다음 스레드로 넘어간다.
    (이를 문맥 교환이라 한다).

[왜 잠금이 뚫릴까?]

  • 스레드 T1이 "어? 잠금 안 걸려있네?(확인)" 하는 순간에
    하필 타임 슬라이스 아웃이 발생해 CPU를 뺏긴다.
    아직 "내가 잠금! 걸어야 하는..(잠금 설정)"을 실행하지 못한 상태인 것이다.
    그때 나타난 스레드 T2도 "어? 잠금 안 걸려있네?" 하고 임계구역에 들어간다.
    이후 다시 차례가 돌아온 T1은 아까 확인해 둔 대로 그대로 임계구역에 진입한다. 결과적으로 두 스레드가 모두 임계구역에 들어가는 대참사가 발생한다.

"세마포어가 있는데 왜 굳이 '모니터'가 등장했을까?"

  • 세마포어는 여러 개의 자원을 관리할 수 있는 훌륭한 도구이다.
    들어가기 전 wait()를 나올 때 post()를 호출하면 된다.
    그런데 문제는 사람(개발자)이 실수한다는 데 있다.

[왜 모니터가 필요할까?]

  • 순서 오류 (post() -> wait())

    • wait()로 자원을 획득하고, post()로 반납해야 하는데
      순서가 바뀌면 데드락(교착상태) 이 발생하거나 자원 보호가 무너진다.
  • 누락 오류 (wait() 또는 post() 생략)

    • 임계 구역(Critical Section) 진입 전 wait()을 빼먹으면
      경쟁 상태(Race Condition)가 발생한다.
      반대로 post()를 빼먹으면 세마포어 카운트가 고갈되어
      다른 프로세스가 영원히 대기하게 된다.
  • 중복 사용

    • 이미 자원을 획득한 상태에서 또 wait()를 호출하면
      스스로 블록되거나 시스템이 멈춘다.
  • 이런 휴먼 에러를 막기 위해 등장한 것이 모니터이다.
    모니터는 캡슐 같습니다. 개발자가 일일이 잠금을 걸고 푸는 게 아니라
    스레드가 모니터에게 "나 이거 할게"라고 요청만 하면
    모니터가 내부적으로 알아서 줄을 세우고(Queue) 동기화를 처리해 준다.
    자바(Java) 언어의 synchronized 키워드가 바로
    이 모니터를 구현한 가장 대표적인 예시이다.


"우선순위 역전: 대장 스레드가 부하 스레드 때문에 멈춘다고?"

  • 운영체제에는 스레드마다 '우선순위(VIP 등급)'가 있다.
    당연히 우선순위가 높을수록 먼저 실행되어야 하는데
    동기화(잠금) 때문에 이 정상적인 흐름이 꼬여버리는 현상이 발생한다.

[왜 역전될까?]

  1. 가장 낮은 스레드(T9)가 먼저 임계구역에 들어가 열쇠를 쥐고 있다.

  2. 가장 높은 우선순위 스레드(T1)가 임계구역에 들어가려 하지만
    T9가 열쇠를 쥐고 있으니 대기한다.

  3. 이때 임계구역을 쓰지 않는 중간 우선순위 스레드(T5)가 등장한다.
    T5는 T9보다 우선순위가 높으니 CPU를 빼앗아 먼저 실행된다.

  4. 결과적으로: T9는 CPU를 뺏겨 열쇠를 반납하지 못하고,
    T1은 T9가 열쇠를 반납하지 않아 무한정 기다리게 된다.
    가장 높은 T1이 가장 늦게 실행되는 모순(우선순위 역전)이 발생한 것이다.

[어떻게 해결할까? - 우선순위 상속]

  • 가장 직관적인 해결책은 "우선순위 상속(Priority Inheritance)"이다.
    T1(VIP)이 T9(쫄병)을 기다려야 하는 상황이 오면
    T1이 자신의 높은 우선순위를 T9에게 잠시 빌려준다(상속).
    그러면 T9가 VIP 등급이 되어 T5(중간)에게 방해받지 않아 빠르게 작업을 끝낼 수 있고, 열쇠를 반납한 즉시 T1이 다시 들어가 작업을 수행할 수 있게 된다.
profile
개발을 즐겨보자.

0개의 댓글