Apple/WWDC 2021

[WWDC 2021] Protect mutable state with Swift actors

Dev_Pingu 2021. 11. 30. 23:00
반응형

안녕하세요 Pingu입니다. 🐧

 

오늘은 WWDC 2021의 "Protect mutable state with Swift actors"이라는 영상을 보고 정리한 글을 써보려고 합니다.

 

Actor는 Swift 프로그래밍을 할 때 가변 상태를 보호하기 위해 사용합니다. Class, Struct와 비슷하게 사용하는데요, 이를 자세히 알아보도록 하겠습니다.

Protect mutable state with Swift actors

동시성 프로그램을 만들 때 어려운 문제중 하나는 data races입니다. Data races는 두 개의 다른 스레드가 동일한 데이터에 접근하는데, 둘 중 하나가 쓰기 작업일 때 발생합니다. 이는 디버깅하기도 어려워서 문제가 발생해도 찾기가 힘들죠.

 

위와 같이 간단하게 value를 1씩 증가시키는 기능을 갖고 있는 Counter 클래스를 만들었습니다. 그리고 2개의 concurrent task를 실행해서 값을 증가시키는 작업을 구현했다고 하면, 이는 잘못된 구현입니다. 실행에 따라 1을 얻고 2를 얻을 수도 있고 2를 얻고 1을 얻을 수도 있죠. 즉 실행할 때마다 결과가 달라지는 문제가 발생합니다.

 

Data races는 디버깅하기도 어려워서 방금같이 간단한 문제도 찾기 어려울 수 있습니다. 또한 OS의 스케줄러는 프로그램을 실행할 때 마다 concurrent task를 다르게 실행할 수 있어서 발생한 문제 자체도 항상 바뀔 수 있죠.

 

이러한 Data races는 shared mutable state(변경 가능한 공유 상태)로 인해 발생합니다. 즉 데이터가 변경되지 않거나 다른 스레드와 공유되지 않는 경우엔 발생하지 않습니다. 따라서 Data races를 피하는 한 가지 방법은 value semantic을 사용해서 shared mutable state를 제거하는 것입니다. 예를 들어 Swift에서 let을 사용해서 변수를 만들면 변경이 불가능해져서 data races로부터 안전합니다.

 

위 코드를 보며 value semantics를 이해해봅시다. 위 코드에서는 값이 있는 배열 array1을 만들고 array2에는 만들어진 array1을 할당합니다. 그런 뒤 각 복사본에 다른 값을 추가한 뒤 print 해보면, 서로 다른 값이 들어있는 것을 알 수 있습니다. 즉 Swift 표준 라이브러리의 대부분의 타입은 위와 같이 value semantics을 사용하므로 data races를 피할 수 있도록 설계되어있습니다.

 

그럼 아까 data races가 발생했던 예제에서 data races를 제거해봅시다. 아까와 다르게 Counter를 클래스가 아닌 구조체로 바꾸면 됩니다. 구조체 프로퍼티를 수정하기 위해서는 mutating 키워드도 붙여줘야합니다.

그런 뒤 아까와 같이 counter를 객체를 만들어서 수정하려고 하면 counter가 let으로 선언된 변수라서 변경할 수 없다고 합니다. 따라서 var로 수정한 뒤 실행하면 될 것 같지만.. 이렇게 하면 counter가 두 개의 concurrent task에서 참조되므로 data races는 해결되지 않고 다시 발생합니다.

 

다행히 컴파일러에서 이러한 코드를 문제 삼기 때문에 컴파일되지는 않습니다. 그럼 어떻게 해야 해결할 수 있을까요?

 

단순히 data races를 해결하기 위해서는각각의 concurrent task 내부에 mutable 변수로 counter를 만들어 처리하면 됩니다. 물론 이건 원하는 결과가 아닙니다.

 

원하는 결과를 얻기 위해 동시성 프로그램에서 shared mutable state가 있는 경우 이로 인해 data races를 발생하지 않도록 동기화가 필요합니다. Atomics(원자성), Locks, Serial dispatch queue와 같은 것들이 이런 문제를 해결할 수 있습니다. 이런 것을 사용하면 장점도 있긴 하지만 모두 주의해서 사용하지 않으면 data races는 해결되지 않을 거예요.

 

Actors

그래서 등장한 것이 Actor입니다. Actor는 shared mutable state를 위한 동기화 메커니즘으로, Actor에는 자체적인 상태가 있으며 해당 상태는 프로그램의 나머지 부분과 분리되어있습니다. 따라서 해당 상태에 접근하는 유일한 방법은 Actor를 통해서만 가능하며, Actor를 통과할 때마다 Actor의 동기화 메커니즘은 다른 코드가 Actor의 상태에 동시에 접근하지 않도록 해줍니다. 이는 Lock, Serial Dispatch Queue를 사용해서 직접 처리하는 것과 동일한 상호 배제효과를 내지만 Actor는 Swift에서 기본적으로 제공해주므로 편리합니다. 또한 동기화를 수행해주지 않으면 컴파일러가 오류를 발생시킵니다.

 

Actor는 Swift의 새로운 타입입니다. 이는 기존에 존재하던 Swift의 타입들과 동일한 기능을 제공하며 프로퍼티, 메서드, 생성자, subscript 등을 가질 수 있습니다. 또한 프로토콜을 채택하고 extension을 사용할 수도 있습니다. Actor의 목적은 Shared mutable state를 표현하는 것이므로 Actor는 Class와 동일하게 참조 타입입니다.

 

Actor 타입의 특징은 인스턴스 데이터를 프로그램의 나머지 부분과 분리하고 해당 데이터에 대한 동기화된 접근을 보장한다는 것입니다. 이 핵심 아이디어가 Actor에서 가장 중요하다고 할 수 있다고 합니다.

 

그럼 간단하게 사용해봅시다.

 

아까 구현했던 Counter를 Actor 타입으로 수정해봅니다. Counter에 대한 value라는 프로퍼티와 이를 1 증가시키는 increment() 메서드는 그대로 존재합니다. 아까와 차이점은 Actor는 value에 동시에 접근되지 않도록 만들어준다는 것 입니다. 즉 만약 increment() 메서드가 호출되면 완료될 때까지 실행됩니다. 이러한 보장은 Data races가 발생하지 않게 해 줍니다.

 

아까와 같이 2개의 concurrent Task를 만들어서 각각의 Task에서 동일한 인스턴스에 increment()를 호출합니다. Actor의 내부 동기화 메커니즘은 하나의 increment()가 완료된 후에 다음 increment()가 실행되도록 해줍니다. 따라서 실제로 실행해보면 하나가 먼저 처리되고 그다음이 처리되는 것을 볼 수 있을 거예요.

 

그럼 여기서 두 번째로 실행되는 concurrent Task는 어떻게 해서 먼저 실행 중인 increment()를 기다린 뒤에 실행하는 걸까요? Swift에는 이를 위한 메커니즘이 있다고 합니다.

 

외부에서 Actor와 상호작용할 때마다 비동기로 처리됩니다. 만약 Actor가 사용 중이라면 사용 중인 CPU가 Actor에 접근 중인 코드를 일시 정지하고 다른 작업을 수행합니다. 이후에 Actor의 사용이 끝나면 정지해둔 코드를 다시 실행해서 Actor에 접근합니다. 위 코드에서 await 키워드는 Actor에 대한 비동기 호출이 일시 정지될 수도 있음을 나타냅니다.

 

좀 더 많은 작업을 수행하는 코드로 Actor를 좀 더 이해해봅시다.

 

위 코드는 value를 다시 0으로 설정한 뒤 적절한 횟수만큼 increment()를 호출해서 value를 새로운 값으로 가지고 오는 resetSlowly() 메서드입니다. 위 메서드는 Counter Actor 타입의 Extension에 정의되어있어 Actor 내부에 있습니다. 즉 value값을 0으로 재설정하기 위해 Actor의 상태에 직접 접근할 수 있습니다. 또한 increment()과 같이 Actor의 다른 메서드를 동기적으로 호출할 수도 있으며, 이미 Actor에서 실행 중이란 것을 알고 있어서 대기도 필요하기 않습니다. Actor의 동기 코드는 중단 없이 완료될 때까지 실행되므로 동시성 프로그래밍에서 주의할 점을 고려할 필요가 없죠.

 

Actor reentrancy

동기 코드는 중단 없이 실행되지만 아까 Actor는 시스템의 다른 비동기 코드와 상호작용한다고 했었는데요, 비동기 코드와 Actor에 대해 알아보기 위해 다음 예를 살펴봅시다.

위 코드는 이미지 다운로드 Actor입니다. 다른 서비스에서 이미지를 다운로드하는 역할을 합니다. 또한 다운로드한 이미지를 캐시에 저장해서 동일한 이미지는 여러 번 다운로드하지 않도록 합니다.

 

로직은 간단합니다. 캐시를 확인하고 캐시에 이미지가 없다면 다운로드한 뒤 캐시에 저장하고 나서 반환합니다. 이는 Actor에서 실행되는 코드이므로 data races로부터 안전합니다. 따라서 원하는 수의 이미지를 동시에 다운로드할 수 있습니다.

 

Actor의 동기화 메커니즘은 한 번에 하나의 작업만 캐시 인스턴스에 접근하도록 보장하므로 캐시가 손상될 수 있는 경우는 없다고 생각할 테지만, 여기서 await 키워드가 문제를 발생시킵니다.

 

await가 발생할 때마다 해당 시점에서 함수는 일시 정지될 수 있음을 의미합니다. 프로그램의 다른 코드가 실행될 수 있도록 CPU를 포기해서 전체 프로그램의 상태에 영향을 줍니다. 함수가 다시 실행되는 시점에 전체 프로그램 상태가 변경되며, await 이후에 유지되지 않을 수 있는 상태를 정의하지 않았는지 확인하는 것이 중요합니다.

 

예를 들어 동일한 이미지를 동시에 가지고 오려고하는 두 개의 concurrent task가 있다고 해보겠습니다. 첫 번째는 캐시에 이미지가 없음을 확인하고 서버에서 웃고있는 고양이 이미지 다운로드를 시작한 뒤 다운로드할 동안 다른 프로그램을 실행하기 위해 일시 정지됩니다. 이렇게 첫 번째 작업이 이미지를 다운로드하는 동안 동일한 URL의 서버에 새로운 이미지가 올라올 수 있습니다. 

두 번째 작업이 동일한 URL로 이미지를 가지고오려고 하는데, 첫 번째 다운로드가 아직 완료되지 않아 캐시에 존재하지 않고 동일한 URL이미지 다운로드를 시작합니다. 물론 이 경우에도 다른 프로그램을 실행하기 위해 일시정지됩니다. 근데 이 때는 이미지가 변경되어 울고 있는 고양이 이미지를 다운로드합니다.

 

잠시 후 첫 번째 다운로드가 완료되고 해당 작업이 Actor에서 실행을 다시 시작하면, 캐시에 이미지를 저장하고 웃고 있는 고양이 이미지를 반환합니다.

 

또 잠시 후 두 번째 다운로드가 완료되고 캐시에 울고 있는 고양이 이미지로 덮어쓴 뒤 반환합니다. 즉 이렇게 하면 동일한 URL로 이미지를 다운로드하였지만 다른 이미지를 얻게 됩니다. 이미지를 캐시 하면 동일한 URL에 대해 동일한 이미지를 가지고 올 줄 알았는데, 캐시 이미지가 변경된 것을 알 수 있습니다. Actor가 low-level의 data races는 없지만 await로 인해 버그가 발생한 것이죠.

 

수정할 것은 await후에 잘 수행되는지 확인하는 것입니다. 이를 위해 일시정지 후 다시 실행할 때 캐시에 이미 값이 있으면 원래 버전을 유지하고 새로운 버전을 버리도록 하던지, 동일한 URL에 대해서는 중복 다운로드를 아예 못하게 해 버리면 될 거예요.

 

Actor reentrancy는 deadlock을 방지하고 forward progress를 보장하지만 await가 존재한다면 좀 더 주의해서 사용해야 합니다. 이를 잘 설계하기 위해서는 동기 코드 내에서 Actor의 상태 변경을 수행하면 됩니다. 가장 좋은 것은 동기 함수 내에서 수행해서 모든 상태 변경이 캡슐화되도록 하는 것입니다.

 

상태 변경은 일시적으로 Actor를 일관성 없는 상태로 만드는 것을 포함하므로 await 전에 일관성을 복원해야만 합니다. await는 정지할 수 있는 지점이므로 코드가 다시 시작되기 전에도 프로그램은 계속 실행되고 있습니다. 따라서 다시 시작되면 global state, clocks, timer와 같은 것들을 확인해줘야 합니다.

 

그럼 이제 Actor isolation에 대해 알아보겠습니다.

 

Actor isolation

Actor isolation은 Actor 타입의 동작의 기본입니다. 아까 Actor 외부와 비동기 상호작용을 통해 Swift가 Actor isolation을 보장하는 방법에 대해 알아봤었는데요, 이번 영상에서는 Actor isolation이 프로토콜 채택, 클로저, 클래스를 비롯한 다른 언어 기능과 상호작용하는 방식에 대해 알아본다고 합니다.

 

다른 타입과 마찬가지로 Actor는 프로토콜을 사용할 수 있습니다. 예를 들어 위와 같이 LibraryAccount Actor가 Equtable 프로토콜을 따르게 합니다. idNumber를 통해 같은 값인지 비교하게 되는데, 해당 메서드는 static이므로 인스턴스가 없어 Actor와 분리되지 않습니다. 대신 외부에서 두 개의 Actor를 매개변수를 받아 처리합니다. 위 구현은 Actor의 immutable 상태에만 접근되기 때문에 아무런 문제가 없습니다.

 

이번에는 LibraryAccount가 Hashable 프로토콜을 따르도록 해봅시다. 그렇게 하려면 위와 같이 hash(into:) 메서드를 구현해야 하며, Swift 컴파일러는 문제를 발견합니다.

 

위와 같이 Hashable을 준수하게 되면 함수를 Actor 외부에서 호출할 수 있지만 hash(into:) 함수가 비동기가 아니므로 Actor isolation을 유지할 방법이 없다는 것을 의미합니다.

 

이를 수정하기 위해서 메서드를 nonisolated로 만들면 됩니다. nonisolated는 이 메서드가 Actor 외부에 있는 것으로 처리된다는 것을 의미합니다. nonisolated 메서드는 Actor 외부에 있는 것처럼 처리되므로 Actor의 mutable state를 참조할 수 없습니다. 물론 위 코드에서 idNumber는 변경할 수 없으므로 문제는 없습니다. 하지만 bookOnLoan 프로퍼티와 같은 array로 hash를 시도하면 외부에서 mutable state에 접근하면 data races를 허용하므로 오류가 발생합니다.

 

다음으로 클로저에 대해 알아봅시다. 클로저는 하나의 함수에서 정의된 작은 함수로 다른 함수로 전달되어 나중에 호출될 수도 있습니다. 함수와 마찬가지로 클로저는 Actor로부터 isolated 될 수도 nonisolated 될 수도 있습니다. 위 코드는 대출한 책들 중 일부를 읽고 총 읽은 페이지 수를 반환합니다.

 

reduce 호출에는 read()를 수행하는 클로저가 포함되며 readSome() 호출에는 await는 없습니다. Actor isolated 함수인 read()에서 만들어진 이 클로저 자체가 Actor isolated이기 때문입니다. 따라서 reduce 작업이 동기적으로 실행되고 동시 접근을 발생할 수 있는 다른 스레드로 이동할 수 없어서 위 코드가 안전하다는 것을 알 수 있습니다.

 

예제를 조금 수정해서, 당장 읽는 게 아닌 나중에 읽는 작업으로 바꿔봅시다.

 

위와 같이 detached task를 만듭니다. 이는 Actor가 수행하는 다른 작업과 클로저를 동시에 실행합니다. 다라서 클로저는 Actor에 있을 수 없고, 그로 인해 data races가 발생합니다. 따라서 위 클로저는 Actor와 nonisolated 합니다. read() 메서드를 호출하려면 await에 표시된 대로 비동기로 호출해야 합니다.

 

지금까지 코드가 Actor 내부에서 실행되는지 외부에서 실행되는지를 알 수 있는 Actor isolation에 대해 알아봤습니다. 그럼 이제는 actor isolation과 data에 대해 알아보겠습니다.

 

앞선 LibratryAccount에서 Book 타입이 뭔지는 몰랐는데요, 만약 Book이 구조체라고 해봅시다. 이는 LibraryAccount Actor의 인스턴스에 대한 모든 상태가 자체적으로 포함된다는 것이므로 좋은 결정입니다. 

구조체로 정의된 Book을 무작위로 선택했을 땐 Book의 복사본을 얻게 되며 해당 복사본에 대한 변경사항은 Actor에게 영향을 주지도 않고 그 반대도 마찬가지입니다.

 

하지만 Book이 클래스로 정의되면 달라집니다. LibraryAccount는 이제 Book 클래스의 인스턴스를 참조하게 됩니다. 아직 문제는 없어요.

 

위 코드에서 Book을 무작위로 선택하는 메서드를 호출하면 어떻게 될까요? 이제 Actor 외부에서 공유된 Actor의 mutable state에 대한 참조가 있습니다. 따라서 data races가 가능해집니다. 이제 책의 title을 수정하면 Actor 안에서 접근할 수 있는 state가 수정됩니다.

 

visit() 메서드에는 Actor에 존재하지 않기 때문에 해당 수정이 data races를 발생시킬 수 있습니다. 즉 값 타입과 Actor는 함께 사용해도 안전하지만 참조 타입과 Actor는 함께 사용하면 문제를 발생시킬 수 있습니다.

 

참조 타입과 함께 사용해도 안전한 타입의 이름은 Sendable입니다.

Sendable Type

Sendable 타입은 다른 Actor 사이에 값을 공유할 수 있는 타입입니다. 하나의 위치에서 다른 위치로 값을 복사하고 두 위치가 서로 간섭하지 않고 해당 값의 복사본을 안전하게 수정할 수 있을 때 Sendable 타입이 될 수 있습니다.

 

이전에도 언급한 대로 각각의 복사본은 독립적이므로 값 타입은 Sendable입니다.

 

Actor 타입은 mutable state에 대한 접근을 동기화하므로 Sendable입니다.

 

Class는 잘 구현된 경우에만 Sendable입니다.

 

예를 들어 클래스와 모든 하위 클래스가 immutable data만 가지고 있는 경우 Sendable이라고 할 수 있습니다. 또는 클래스가 내부적으로 Lock과 같은 동기화를 수행해서 스레드로부터 안전한 경우 Sendable이 될 수 있습니다. 하지만 대부분의 경우 이를 만족하지 못하므로 Sendable이 아니며, 함수는 언제나 Sendable일 필요는 없기 때문에 Actor 간에 안전하게 전달할 수 있는 새로운 함수 타입이 있는데, 이는 잠시 후에 알아보도록 하겠습니다.

 

Actor(사실상 모든 concurrent code)는 주로 Sendable 타입으로 통신해야 합니다. Sendable 타입은 data races로부터 코드를 보호하며 이는 Swift가 정적으로 검사를 시작하는 프로퍼티이며, 위와 같이 Sendable이 아닌 것을 전달하는 경우에는 오류가 발생됩니다.

 

타입이 Sendable인건 어떻게 알 수 있을까요? Sendable을 프로토콜이고 이를 따르면 Sendable 타입이라고 할 수 있습니다. 그런 뒤 Swift는 타입이 Sendable인지 확인합니다. 위 코드에서 Book 구조체는 모든 프로퍼티가 Sendable 타입이라면 Sendable입니다.

 

만약 Author 타입이 클래스라면 authors 프로퍼티는 Sendable이 아니게 되어 위와 같이 오류를 발생시킵니다.

 

제네릭 타입의 경우 Sendable 여부는 제네릭 인수에 따라 달라질 수 있습니다. 적절할 때 Sendable을 전파할 수 있도록 조건을 달 수 있습니다. 위와 같이 Pair 타입은 인수가 모두 Sendable인 경우에만 Sendable이 됩니다.

 

값을 동시에 공유해도 안전한 타입에 Sendable 적합성을 도입하는 것이 좋다고 합니다. 그러한 타입을 Actor 안에서 사용하면 Swift가 Actor 간에 Sendable을 적용하기 시작하고 코드가 실행 준비됩니다.

 

함수 자체는 Sendable이 될 수 있으며, 즉 Actor 간에 함숫값을 전달하는 것은 안전하다는 것을 의미합니다. 이는 data races를 방지하기 위해 클로저가 할 수 있는 작업을 제한하는 클로저의 경우 특히 중요하다?라고 하는데.. 이해가 잘 안 되네요... (This is particularly important for "closures" where it restricts what the "closure" can do to help prevent data races.)

 

예를 들어 Sendable closure은 mutable 지역 변수를 캡처할 수 없습니다. 이는 지역 변수가 data races를 허용하기 때문입니다. 즉 클로저가 캡처 가능한 것은 Sendable 타입이며 클로저를 사용해서 Actor 간 non-Sendable 타입을 이동시킬 수는 없습니다.

 

마지막으로 동기식 Sendable 클로저는 Actor isolated 하지 않습니다. 외부에서 Actor에게 코드가 실행될 수 있기 때문이죠.

 

사실 지금까지 봐왔던 예들은 Sendable 클로저 아이디어를 사용하고 있었습니다.

 

detached task를 생성하는 작업은 함수 타입에 @Sendable 키워드가 붙은 Sendable 함수를 사용했습니다. 이번 영상 시작 부분에서 값 타입 Counter를 만들던 예를 기억하시나요? 해당 예에서는 두 개의 다른 클로저에서 동일한 값을 동시에 수정하려고 했었죠. 이는 Sendable 지역 변수에 대한 data race를 발생시키는 것입니다.

 



하지만 detached task에 대한 클로저는 Sendable 이므로 Swift는 오류를 발생시킵니다. Sendable 함수 타입은 동시 실행이 발생할 수 있는 위치를 나타내는 데 사용되므로 data races를 방지합니다.

 

다른 예를 한 번 보겠습니다. detached task에 대한 클로저는 sendable 하므로 Actor에서 분리되면 안 된다는 것을 알고 있습니다. 따라서 상호작용은 비동기로 발생해야 합니다.

Sendable 타입 및 클로저는 mutable state가 Actor 간에 공유되지 않고 동시에 수정할 수 없는지 확인해서 Actor isolation을 유지하는데 도움이 됩니다.

 

Main Actor

그럼 마지막으로 Main Actor라는 특별한 Actor에 대해 알아봅시다.

 

앱을 빌드할 땐 main 스레드를 많이 고려합니다. UI 렌더링이 발생하거나 사용자 이벤트를 처리하는 스레드가 main 스레드인데요, 모든 작업을 main 스레드에서 실행하고 싶지 않을 수 있습니다. 예를 들어 느린 입출력 작업이나 서버와 많은 작업을 처리하면 UI가 정지될 수 있죠. 따라서 main 스레드가 UI와 상호작용할 때는 오래 걸리는 작업은 main 스레드에서 빨리 빠져나오도록 해야 합니다.

 

따라서 가능하면 main 스레드에서 작업한 뒤 main 스레드에서 실행해야 하는 특정 작업이 있을 때마다 DispatchQueue.main.async를 호출합니다.

 

위 코드는 좀 친숙해 보이는데요, 사실 main 스레드와 상호작용하는 것은 Actor와 상호작용하는 것과 비슷합니다. 이미 main 스레드에서 실행 중임을 알고 있다면 UI 상태에 안전하게 접근하고 업데이트할 수 있습니다. main 스레드에서 실행하지 않는 경우 비동기로 상호작용해야 하죠. 이것이 바로 Actor가 작동하는 방식입니다.

 

MainActor라고 부르는 main 스레드를 나타내는 특별한 Actor가 있습니다. 이는 일반 Actor와 다음 2가지가 다릅니다.

  • MainActor는 Main Dispatch Queue를 통해 모든 동기화를 수행합니다. 즉 런타임 관점에서 MainActor는 DispatchQueue.main을 사용해서 교체할 수 있습니다.
  • Main 스레드에 있어야 할 코드와 데이터가 여기저기 존재합니다. SwiftUI, AppKit, UIKit 및 시스템 프레임워크에 존재하죠

Swift 동시성을 사용하면 MainActor를 표시해서 main actor에서 실행되어야 한다고 선언할 수 있습니다. 위 코드에서 checkedOut() 메서드는 항상 MainActor에서 실행됩니다.

 

MainActor 외부에서 호출하는 경우 await 키워드를 사용해서 비동기적으로 수행되도록 해야 합니다. main 스레드에서 실행되어야 하는 코드를 MainActor에 있는 것으로 표시하면 DispatchQueue.main을 언제 사용해야 하는지 더 이상 고려할 필요가 없습니다. Swift가 해당 코드를 항상 main 스레드에서 실행되도록 하기 때문이죠.

 

타입은 MainActor에도 위치시킬 수 있으므로 모든 멤버와 하위 클래스가 MainActor에 존재하게 됩니다. 이는 대부분 모든 것이 Main 스레드에서 실행되어야 하는 UI 관련 코드를 작성할 때 유용합니다.

 

이번 영상에서 Actor isolation을 사용하고 실행을 동기적으로 실행하기 위해 Actor 외부에서 비동기 접근을 요구해서 Actor가 동시 접근으로부터 shared mutable state를 보호하는 방법을 알아봤습니다. 하지만 await를 사용할 땐 주의해야 한다는 점도 알아봤습니다. 값 타입과 Actor를 함께 사용하면 data race를 제거할 수 있었지만 참조 타입과 Actor를 함께 사용할 땐 Sendable 타입을 사용해야 한다는 것도 알아봤습니다.

 

마지막으로 UI 관련 코드를 MainActor를 사용해서 항상 main 스레드에서 실행할 수 있도록 할 수 있다는 것도 알아봤습니다.

 

앱에서 Actor를 사용하는 자세한 방법은 아래 영상을 확인하라고 하네요.

Actor를 포함한 Swift의 동시성 모델 구현에 대해 자세히 알아보려면 아래 영상을 참고하라고 합니다.

Actor는 Swift의 동시성 모델의 핵심 부분으로 async / await와 structured concurrency와 함께 동작해서 정확하고 효율적인 동시성 프로그램을 쉽게 구축할 수 있도록 도와줍니다.

 

이번 영상은 이렇게 끝이 납니다.

 

Actor는 영상을 몇 번 보고 정리한 건데도 이해가 잘 안 되는 부분이 있는 어려운 개념인 듯합니다. 하지만 아주 중요한 내용이니... 더 공부해서 잘 사용할 수 있도록 해야겠네요..

 

감사합니다.

반응형