상세 컨텐츠

본문 제목

[Swift] Data Race

iOS/Swift

by kimrindev 2025. 5. 27. 13:11

본문

Data Race 란? 

여러쓰레드에서 동시에 하나의 데이터에 접근하고, 그중 하나의 쓰레드가 쓰기작업을 하게될경우 예측할수 없는 결과가 발생하는 문제이다.

 

예제: 클래스를 여러 Task에서 동시에 접근할경우 (Data Race 발생 )

final class Counter {
    var value = 0
    func increment() {
        value += 1
    }
}

let counter = Counter()

Task {
    for _ in 0..<1000 {
        counter.increment()
    }
}

Task {
    for _ in 0..<1000 {
        counter.increment()
    }
}

 

class는 참조 타입이며 Task {} 는 비동기적이고 병렬적인 작업 단위인데. ( 내부적으로 별도 쓰레드에서 실행됨 )
여러 Task가 동시에 동일한 class 인스턴스를 참조하고, 동일한 속성에 접근하면 문제가 발생한다.
그 이유는 value += 1이 단순한 한 줄 같지만 실제로는 3단계 작업으로 이루어지기 때문!

 

Value += 1 의 내부동작  사실 3단계 작업으로 나뉜다.

  1. value를 읽기 (read) → 현재 값을 가져오기
  2. +1 로 연산 (modify) → 새값 계산
  3. 결과를 다시 value에 쓰기 (write) → 저장

이과정에서 Task A와 Task B가 거의 동시에 실행되면,

예를 들어 value가 5일 때 다음과 같은 상황이 발생할 수 있다.

Task A Task B 설명
읽기: 5   Task A가 value 읽음
  읽기: 5 Task B도 value 읽음
연산: 6   Task A가 +1
  연산: 6 Task B도 +1
쓰기: 6   Task A가 6을 저장
  쓰기: 6 Task B도 6을 저장 (Task A의 결과 덮어씀)

 

 

그래서 value는 2번 증가해야 하는데 실제로는 1번만 증가한 것처럼 보이게 된다.

이게 DataRace이다.

 

 

즉 class는 동일한 인스턴스를 여러 쓰레드(Task)에서 공유할 수 있기 때문에, 비동기 환경에서는 읽기-수정-쓰기 과정 중 충돌이 발생할 수 있고 이로 인해 예상과 다른 결과가 나오는 문제가 생긴다.

 

그래서 actor가 나오기전 동시성 제어 방식은

1. GCD (Grand Central Dispatch)

2. NSLock / os_unfair_lock

3. OperationQueue / NSOperation

이와같은 방식으로 직접 동시성 문제를 제어해야했다.

// GCD (Grand Central Dispatch)
final class Counter {
    private let queue = DispatchQueue(label: "counter.queue")
    private var value = 0

    func increment() {
        queue.sync {
            value += 1
        }
    }
}


//  NSLock / os_unfair_lock
final class Counter {
    private var value = 0
    private let lock = NSLock()

    func increment() {
        lock.lock()
        value += 1
        lock.unlock()
    }
}

// OperationQueue / NSOperation
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1 // Serial로 설정

queue.addOperation {
    // 내부 상태 변경
}

 

 

항상 우리가 직접 “공유 자원에 대한 접근을 직렬화” 해야 했고,

 

코드가 늘어나고 관리 포인트가 많아지거나 어떤 queue를 어떤 객체에 적용했는지 전역적으로 추적하기 어렵고

Deadlock 또는 main thread blocking 발생할수 있는 여러 단점이 있었고

 

actor를 통해서 GCD나 Lock을 직접 관리할 필요 없이, Swift 컴파일러가 자동으로 안전한 환경을 제공받을수 있었다 .

즉 actor는 “Swift 언어 차원에서 안전한 동시성 모델을 보장해주는 첫 reference type” 인 셈이다.

 

관련글 더보기