CS/OS

[OS] 경쟁 상태(Race Condition)과 다양한 해결 전략

leejunkim 2025. 10. 16. 09:43

WeeklyPaper: 멀티스레드 환경에서 발생하는 대표적인 문제 중 하나인 경쟁 상태(Race Condition)에 대해 설명하고, 이를 해결하기 위한 다양한 전략을 설명해보세요.


Race Condition

경쟁 상태 (Race Condition)이란 두개의 프로세스가 공통 자원을 병행적으로 (concurrently) 읽거나 쓰는 동작을 할때, 공용 데이터에 대한 접근이 어떤 순서로 이루어졌는지에 따라 실행 결과가 달라지는 상황을 의미한다.

  • 간단히 말하면 말 그대로 Race 처럼 두개의 스레드가 하나의 자원을 놓고 경쟁하는 것을 말한다.

Critical Section (임계 영역)

운영 체제에서 여러 프로세스가 데이터를 공유하면서 수행될 때 각 프로세스에서 공유 자원에 접근하는 프로그램 코드 부분을 의미한다. 즉, 프로세스간에 공유 자원을 접근하는데 있어서, 문제가 발생하지 않도록 공유 자원의 독점을 보장해주어야 하는 영역이다.

 

문제점 정리

주로 세가지의 문제에 직면한다.

  1. Mutual exclusion (상호 배제)
    • 두개 이상의 프로세스가 공용 데이터에 접근을 하는 것을 막기 위해서, 한 프로세스가 공용 데이터를 사용하고 있으면 그 자원을 사용하지 못하도록 막고 그 외의 프로세스의 사용을 막는 것을 의미한다.
  2. Deadlock (교착상태)
    • 하지만 상호 배제를 시행하면 또다른 문제가 생길 수 있다. 두가지의 프로세스가 두 자원 모두에 엑세스가 필요한다고 가정했을 때, 프로세스는 두 자원 모두를 필요로 하므로, 모든 리소스를 사용하여 프로그램을 수행할때까지 이미 소유한 리소스를 해제하지 않는다. 이렇게 되면 두 프로세스는 교착 상태에 빠지게 된다.
  3. Starvation (기아 상태)
    • 이 제어 문제는 기아 상태라고도 하는데, 프로세스들이 더 이상 진행을 못하고 영구적으로 블록되어 있는 상태를 의미한다. 두개 이상의 작업이 서로 상대방의 작업이 끝나기를 기다리고 있기 때문에 결과적으로는 아무것도 완료되지 못하는 상태가 된다.

이렇게 쓰레드의 실행 순서를 잘 조절해주지 못하면 이런 문제점들이 생길 수 있다. 

이런 문제점들이 발생하지 않도록 예방할 수 있는 방법들이 존재한다.

 

해결 방안

1. 핵심 잠금 메커니즘 (저수준)

 

Semaphore (세마포어)

 

  • 공유된 자원의 데이터를 여러 프로세스가 접근하는 것을 막는 것이다
  • 정해진 개수(N)의 스레드만 공유 자원에 접근하도록 허용한다. N개의 허용치를 가진 카운터로 생각할 수 있으며, 스레드가 자원을 사용하면 카운트가 줄고 반납하면 늘어난다. 카운트가 0이면 다른 스레드는 대기해야 한다

Mutex (뮤텍스)

 

  • 공유된 자원의 데이터를 여러 스레드가 접근하는 것을 막는 방법이다.
  • 자원에 접근해야 하는 코드 영역(임계 영역, Critical Section)을 lock을 걸어 잠그고, 사용이 끝나면 unlock하여 다른 스레드가 사용할 수 있도록 한다 (오직 하나의 스레드만 잠금을 획득할 수 있다).

동기화 설계 전략 (고수준)

 

Synchronized (동기화 블록/메서드)

  • 자바에서 뮤텍스의 개념을 더 편리하게 사용할 수 있도록 제공하는 키워드다. 내부적으로는 객체의 lock(모니터 락)을 사용하여 한 번에 하나의 스레드만 해당 코드 블록에 접근하도록 제어한다.

불변 객체 활용 (Immutability)

  • 객체가 생성된 후에 그 상태를 절대 변경할 수 없게 만드는 것이다. 데이터가 변하지 않으니 여러 스레드가 동시에 읽어도 아무런 문제가 발생하지 않는다 -> 애초에 수정 자체를 불가능하게 만들어서 race condition를 원천적으로 제거한다.

스레드 로컬 사용 (Thread-Local Storage)

  • 각 스레드마다 자신만의 독립된 저장 공간을 만들어주는 것이다.

원자적 연산 사용 (Atomic Operations)

  • '원자적'이라는 말은 더 이상 쪼갤 수 없는 하나의 단일 연산이라는 뜻이다.
  • 예를 들어, count++는 실제로는 값을 읽고 -> 1을 더하고 -> 결과를 쓰는 3단계로 나뉘지만, 원자적 연산은 이 과정을 누구도 방해할 수 없는 한 번의 동작으로 처리한다.
  • 여러 단계로 나뉘어 발생하던 Race Condition을 단 한 번의 중단 없는 연산으로 만들어 문제를 해결한다. 내부적으로는 CAS(Compare-And-Swap) 같은 락-프리(Lock-Free) 기술을 사용해 효율적으로 처리한다.

Concurrent 자료구조 활용

  •  ConcurrentHashMap처럼 자바 등에서 기본으로 제공하는 스레드 안전(Thread-Safe) 컬렉션을 사용할 수도 있다. 개발자가 직접 동기화 코드를 짜지 않아도, 내부적으로 락을 매우 잘게 나누거나(Fine-grained Lock) 원자적 연산을 활용하여 안전하고 효율적인 동시 접근을 보장한다.

자료

'CS > OS' 카테고리의 다른 글

[OS] 로컬 캐시와 분산 캐시  (0) 2025.10.22