WWDC22: Swift Concurrency로 데이터 레이스 완벽 제거하기

Ios_Roy·2025년 9월 26일

WWDC

목록 보기
6/13
post-thumbnail

동시성 프로그래밍에서 가장 큰 골칫거리는 바로 데이터 레이스(data race)입니다.
Swift Concurrency는 이러한 문제를 구조적으로 해결하며, Swift 6에서는 더욱 엄격하고 안전하게 동작합니다.


목차

  1. 데이터 레이스란?
  2. Swift Concurrency의 핵심 – Task & Actor 격리
  3. 원자성(Atomicity)과 불변성
  4. 작업 순서 지정(Ordering)
  5. Sendable 체크란?
  6. 실전 코드 예시
  7. 실전 아키텍처 적용 및 주의점
  8. 장점과 한계
  9. 결론

1. 데이터 레이스란?

여러 작업(Task, Thread)이 동시에 같은 데이터에 접근하거나 수정하면
서로 엉켜서 데이터가 손상(잘못된 값, 예측불가 결과)되는 현상을 말합니다.


2. Swift Concurrency의 핵심 – Task와 Actor 격리

Task isolation

Swift에서는 Task(작업)를 하나의 보트, Actor를 에 비유합니다.

  • 작업(Task)은 독립적으로 바다를 항해합니다.
  • 각 Task는 처음부터 끝까지 독립적으로 동작, 자체 리소스를 가짐
  • 완전히 격리된 Task는 Data Race에 안전하지만 통신 방법이 필요함

Actor isolation

Actor는 데이터의 안전한 컨테이너, 여러 Task가 동시에 접근하지 못하도록 완전히 격리합니다.

actor Island {
    var food: [Fruit] = []
    func add(food: Fruit) {
        food.append(food)
    }
}
  • 한 번에 하나의 Task만 섬(Actor)에 올라갈 수 있음
  • 모든 코드/상태는 Actor에 격리되어 있음

3. 원자성(Atomicity)과 불변성

데이터를 바꿀 때 한 번에, 중간에 끼어들지 않는 처리를 의미합니다.
Swift Actor는 내부적으로 원자성을 보장하려고 노력하지만
await로 작업이 잠시 쉬면 다른 Task가 들어와 상태가 변할 위험이 있습니다.

예시:

func depositPineapple(island: Island, pineapple: Fruit) async {
    var food = await island.food
    food.append(pineapple)
    await island.add(food: pineapple)
}
  • 위 예시에서 await를 쓰는 순간, 다른 Task가 food를 건드릴 수 있으니 조심해야 합니다!

4. 작업 순서 지정(이벤트 Ordering)

Task가 여러 개일 때, 순서를 맞춰야 안전하게 처리할 수 있습니다.

let stream = AsyncStream<Fruit> { continuation in
    // 이벤트를 비동기로 발생
}

for await fruit in stream {
    await island.add(food: fruit)
}
  • 이벤트 스트림을 순차적으로 처리하며 data race를 피함

5. Sendable 체크란?

Swift 5.7 이후로 Sendable 프로토콜이 도입되어
비동기 환경에서 안전하게 전달 가능한 타입만 Task/Actor 경계에서 쓸 수 있습니다.

struct Pineapple: Sendable {
    let id: Int
}

func transferFruit(_ fruit: Pineapple) async {
    // 안전하게 전달 
}

또한, 컴파일러 수준으로 엄격한 체크 옵션(Minimal/Targeted/Complete)으로 경고와 에러를 잡아냅니다.


6. 실전 코드 예시

Actor 간 데이터 전달, 순서와 atomicity 주의!

actor Bank {
    private var balance: Int = 0

    func deposit(amount: Int) {
        balance += amount
    }

    func withdraw(amount: Int) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }
}

func transfer(bank: Bank, amount: Int) async {
    await bank.deposit(amount: amount)
}

Task {
    await transfer(bank: myBank, amount: 100)
}
  • 동시에 여러 Task가 동작해도 balance 값은 손상되지 않음
  • 만약 중간에 await로 상태 변경이 일어난다면, 불변성/atomicity 보장에 신경써야 함

7. 실전 아키텍처 적용 및 주의점

  • 모든 상태, 코드, 비동기 Task는 격리와 atomicity 원칙 준수!
  • 실시간 처리, 스트림 소비 등은 순서 지정 중요
  • Task/Actor 경계에서 Sendable 타입만 전송
  • 엄격한 Concurrency Checking 옵션으로 에러와 경고 빠르게 확인

8. 장점과 한계

장점한계
데이터 레이스 완벽 차단격리 설계 신중 필요
안전한 동시성 모델성능 최적화 추가 노력
컴파일러 체크로 예방 용이legacy 코드 적용 어려움

9. 결론

Swift의 동시성은 언어 차원에서 Data Race를 막아주는 강력한 도구입니다.
Task 격리, Actor 격리, Sendable 체크, atomicity 관리까지
안전한 동시성 앱, 서버, 서비스 개발에 필수며
Swift 6에서는 더욱 엄격하게 적용됩니다.


profile
iOS 개발자 공부하는 Roy

0개의 댓글