-
Notifications
You must be signed in to change notification settings - Fork 2
[승용] Eliminate data races using Swift Concurrency
- Swift Concurrency는 동시성 프로그램을 쉽게 작성하는 일련의 Swift 언어 기능
- 데이터 경쟁 없이 동시성을 효율적으로 활용하도록 프로그램을 구성하기 위한 방법으로 Swift Concurrency 살펴보기
- 격리(isolation)는 Swift Concurrency 모델의 핵심 아이디어 중 하나
- 잠재적인 Data race가 일어나지 않는 방식으로 데이터가 공유되도록 보장한다.
-
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 사용하기!
-
근데 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이 필요하다.
- 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
-
동시성 프로그램은 여러 가지 작업들이 동시에 진행되기 때문에 작업들의 실행 순서는 실행마다 다를 수 있다.
-
그러나 일관된 순서로의 처리가 필요한 작업들도 있다.
-
Actor는 작업 순서를 지정할 수 없다. Actor는 전체 시스템의 응답성을 유지하기 위해 우선순위가 가장 높은 작업을 먼저 처리해서 우선순위 역전을 방지한다.
-
Swift Concurrency에서 순서 부여를 위한 도구들
- Task -> 코드를 순서에 따라 실행한다
- AsyncStreams -> 이벤트 스트림 모델링 가능
-
Sendable이 도입되었지만 모든 부분에 Sendable을 한 번에 적용하기는 어렵다. 점진적으로 적용해야 한다.
-
그래서 컴파일러 옵션으로 얼마나 엄격하게 Sendable을 적용해야 하는지를 설정 가능함.
-
다른 모듈에서 Sendable을 지원하지 않을 수도 있음 -> 오류가 날텐데, @preconcurrency 를 import 앞에 붙여서 오류 제거할 수 있음
-
Complete checking -> 모든 Data race 검사
권승용 | 김대황 | 김인환 | 유정주 | 윤동주 | 이준복 | 이창준 | 홍승현 |
---|---|---|---|---|---|---|---|
ericKwon95 | qwerty3345 | loinsir | jeongju9216 | yoondj98 | junbok97 | SwiftyJunnos | WhiteHyun |