면접 질문 목록으로 가기
면접 질문실전 질문꼬리 질문

코틀린에서의 경쟁 조건(Race Condition)

skydovesJaewoong Eum (skydoves)||7분 소요

코틀린에서의 경쟁 조건(Race Condition)

경쟁 조건(race condition)이란, 공유 가변 상태(shared mutable state)에 동시에 접근하는 여러 작업의 실행 순서에 따라 프로그램의 정확성이 달라지는 현상을 말합니다. 두 개 이상의 스레드 또는 코루틴이 적절한 동기화(synchronization) 없이 동일한 데이터를 읽고 쓰면, 어떤 작업이 먼저 완료되느냐에 따라 최종 결과가 달라집니다. 경쟁 조건으로 인한 버그는 간헐적으로 발생하고, 재현이 어려우며, 테스트 환경에서는 드러나지 않다가 실제 운영 환경의 높은 부하에서 비로소 나타나는 경우가 많습니다. 면접에서도 동시성 관련 질문으로 자주 출제되는 주제이므로, 핵심 개념과 해결 방법을 확실히 정리해 두는 것이 중요합니다.

이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.

  • 동기화되지 않은 공유 가변 상태 접근이 경쟁 조건을 유발하는 원리를 설명할 수 있습니다.
  • 확인 후 실행(check-then-act) 패턴과 읽기-수정-쓰기(read-modify-write) 패턴이 대표적인 경쟁 조건 원인이라는 점을 파악할 수 있습니다.
  • synchronized, Mutex, 원자적 연산(atomic operations) 등 코틀린에서 제공하는 동기화 기법을 적용할 수 있습니다.
  • 코루틴 한정(confinement) 기법을 활용하여 동시성 코드에서 공유 가변 상태 자체를 제거하는 방법을 익힐 수 있습니다.

경쟁 조건이 발생하는 원리

경쟁 조건이 발생하려면 세 가지 조건이 충족되어야 합니다. 바로 공유 상태, 동시 접근, 그리고 최소 한 번 이상의 쓰기 연산입니다. 두 스레드가 동일한 값을 읽고, 그 값을 기반으로 새로운 값을 계산한 뒤, 결과를 다시 쓰는 상황에서는 각 연산이 서로 엇갈려 실행되면서 일부 업데이트가 유실될 수 있습니다.

var counter = 0

fun increment() {
    val current = counter  // 읽기
    counter = current + 1  // 쓰기
}

두 스레드가 동시에 increment()를 호출하면, 두 스레드 모두 counter를 0으로 읽고, 1을 계산하여, 1을 쓰게 됩니다. 결과적으로 counter는 2가 되어야 하지만 1이 됩니다. 이것이 읽기-수정-쓰기(read-modify-write) 경쟁이며, 읽기와 쓰기가 하나의 원자적 연산으로 수행되지 않기 때문에 발생합니다. 실무에서 이러한 버그는 단위 테스트로 잡아내기 매우 어렵기 때문에, 동시성 코드를 작성할 때 항상 원자성을 고려해야 합니다.

확인 후 실행(Check-Then-Act) 경쟁

또 다른 대표적인 패턴은 확인 후 실행(check-then-act)입니다. 조건을 검사한 뒤 그 결과에 따라 행동을 취하지만, 검사와 행동 사이에 조건이 무효화될 수 있는 패턴입니다.

var balance = 100

fun withdraw(amount: Int): Boolean {
    if (balance >= amount) {   // 확인(check)
        balance -= amount      // 실행(act)
        return true
    }
    return false
}

두 스레드가 동시에 withdraw(80)을 호출하면, 두 스레드 모두 balance를 100으로 확인하고, 조건을 통과한 뒤, 각각 80을 차감합니다. 결과적으로 잔액이 -60이 됩니다. 확인과 차감이 원자적으로 수행되지 않았기 때문에 발생하는 문제이며, 금융 관련 로직에서 이런 버그가 발생하면 심각한 결과를 초래할 수 있습니다.

이 면접 질문은 구독자 전용입니다

Dove Letter를 구독하시면 안드로이드, 코틀린 개발 관련 독점 면접 질문의 전체 내용을 볼 수 있습니다.

구독하기
면접 질문 목록으로 가기