Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[승용] Eliminate data races using Swift Concurrency

Eric Kwon / 권승용 edited this page Sep 2, 2024 · 1 revision

공식문서

  • Swift Concurrency는 동시성 프로그램을 쉽게 작성하는 일련의 Swift 언어 기능
  • 데이터 경쟁 없이 동시성을 효율적으로 활용하도록 프로그램을 구성하기 위한 방법으로 Swift Concurrency 살펴보기
  • 격리(isolation)는 Swift Concurrency 모델의 핵심 아이디어 중 하나
  • 잠재적인 Data race가 일어나지 않는 방식으로 데이터가 공유되도록 보장한다.

Task Isolation

  • Task는 시작부터 끝까지 순차적으로 작업을 수행함

  • 비동기적으로 실행되며 await 키워드를 만나면 언제든지 작업이 중단(suspend)될 수 있음

  • 독립적임(Self-contained)

  • 동시성의 바다에 떠다니는 보트로 생각할 수 있음

  • 어떤 데이터를 각 보트 간에 전달해야 하는데, 값 타입은 복사본을 생성해서 전달

  • 그래서 변경이 가해져도 각 보트 안에서만 변경됨 -> 변경이 local하기 때문에 데이터 경쟁 없음

  • 참조 타입은 참조를 전달, 두 보트가 같은 데이터를 가리키고 있음 -> 보트 A의 데이터 변경이 보트 B의 데이터에도 영향을 미침 -> 데이터 경쟁 가능성 있음 -> 보트가 독립적이지 않게 됨

  • 따라서 각 Task들끼리 공유해도 안전한 타입과 그렇지 않은 타입을 추론할 수 있는 방법이 있어야 함

  • 여기서 Sendable 프로토콜 사용!

  • Sendable 프로토콜로 공유 안전한 타입을 모델링 가능

  • 서로 다른 격리된 Task 사이에 제네릭으로 데이터가 전달된다면 Sendable 준수해야 함

  • Sendable 체킹을 통해 Task들 사이에 Sendable로 전달하는지 컴파일러가 감시 -> 컴파일러가 각 Task가 isolated하게 유지되는지 감시한다는 의미

  • 프로퍼티가 Sendable하면 타입도 Sendable

  • 배열의 요소가 Sendable하면 배열도 Sendable

  • 클래스는 final class가 불변프로퍼티만을 지닐 경우 등 특별한 경우만 Sendable할 수 있음

  • Task의 클로저는 독립적으로 실행되기 때문에 Sendable한 값만 캡처할 수 있음

    • 이는 Task의 구현에 @Sendable로 나타나 있음
  • @Sendable은 함수 타입으로 전송 가능한 함수임을 나타냄

  • 공유해야 하는 값은 Sendable 사용하기!

Actor Isolation

  • 근데 Sendable은 변경 불가능한 데이터 이야기.

  • Data race가 없으면서도 shared mutable state를 공유할 방법이 필요함. -> Actor!

  • Actor는 동시성의 바다에서 섬으로 생각할 수 있음

  • 다른 모든 것과 격리되어 고유한 상태를 가짐

  • Actor를 실행하려면 Task가 필요

  • 한 번에 하나의 Task만이 Actor에 접근 가능

  • 다른 Task는 기다릴 수 있기 때문에 await 키워드로 표시된 지점이 잠재적 suspention point

  • Task와 Actor 사이에서도 non-Sendable 타입이 교환되지 않도록 격리해야 한다.

  • Actor-isolated 한 component들

  • Actor의 인스턴스 프로퍼티 / 메소드 / extension의 메소드 / 전송 불가능한 클로저 / Actor context 내부의 Task

  • Detached Task와 같은 독립적인 맥락(context)에서 생성된 녀석은 actor-nonisolated

  • nonisolated 키워드를 사용하면 어떤 Actor에서도 실행되지 않는 코드 만들 수 있음

  • nonisolated async 코드는 항상 global cooperative pool에서 작동한다.

    • 동시성의 바다에서 보트가 공해에 나와 있을 때만 작동한다고 생각.
    • 그래서 Actor로부터 전송 불가능한 데이터를 갖고 있지는 않은지 고려.
    • 섬(Actor)에서 전송 불가능한 데이터를 가지고 공해로 나올 수 없다.
  • Actor에 진입하거나 빠져나올 때 Sendable 확인이 컴파일러에 의해 자동적으로 이루어진다.

  • Actor 자기 자신도 Sendable하다.

  • MainActor는 프로그램의 UI와 관련된 상태를 많이 가지고 있다

  • 많은 UI 프레임워크 코드와 앱 코드가 MainActor 위에서 실행되어야 한다

  • 그러나 한 번에 하나의 작업만 실행되어야 한다.

  • MainActor의 사용으로 해당 코드가 메인 스레드에서 실행됨을 Swift가 보장하며, Actor와 같이 MainActor가 작업중일 땐 다른 작업자는 접근할 수 없다.

  • 따라서 MainActor로부터 nonisolated 된 context에서 MainActor 코드를 호출하는 경우 await이 필요하다.

Atomicity

  • Actor는 한 번에 하나의 작업만 수행한다.
  • 작업이 끝나면 Actor는 다른 작업을 수행할 수 있다.
    • 프로그램이 계속 진행되므로 Deadlock의 가능성을 피할 수 있음
  • 이러한 과정으로 low-level Data race는 피할 수 있다.
  • 그러나 await 구문 전후의 상태 변화에 의해 High-level Data race가 발생할 수 있다.
  • 이를 피하기 위해서 Actor에 대한 상태 변화는 Actor 내부에서 동기적인것이 좋고
  • nonisolated async 함수 내부에서의 await 전후로 상태변화가 있을 수 있음을 고려해서 코드를 작성해야 한다.
  • transactionally 하게 생각하기
    • Identify synchronous operations that can be interleaved
    • keep async actor operations simple

Ordering

  • 동시성 프로그램은 여러 가지 작업들이 동시에 진행되기 때문에 작업들의 실행 순서는 실행마다 다를 수 있다.

  • 그러나 일관된 순서로의 처리가 필요한 작업들도 있다.

  • Actor는 작업 순서를 지정할 수 없다. Actor는 전체 시스템의 응답성을 유지하기 위해 우선순위가 가장 높은 작업을 먼저 처리해서 우선순위 역전을 방지한다.

  • Swift Concurrency에서 순서 부여를 위한 도구들

    • Task -> 코드를 순서에 따라 실행한다
    • AsyncStreams -> 이벤트 스트림 모델링 가능
  • Sendable이 도입되었지만 모든 부분에 Sendable을 한 번에 적용하기는 어렵다. 점진적으로 적용해야 한다.

  • 그래서 컴파일러 옵션으로 얼마나 엄격하게 Sendable을 적용해야 하는지를 설정 가능함.

  • 다른 모듈에서 Sendable을 지원하지 않을 수도 있음 -> 오류가 날텐데, @preconcurrency 를 import 앞에 붙여서 오류 제거할 수 있음

  • Complete checking -> 모든 Data race 검사

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /