티스토리 뷰

이번 주 스터디에서는 제가 Actor의 isolation에 대해 이야기했는데, 관련 내용을 블로그에도 한번 정리해 보겠습니닷 

잘못된 정보가 있다면 언제든 댓글로 알려주세요 .. 🥹

 

Actor의 기본 원리는 isolation(격리)을 통해 Actor를 분리하고, 각각의 Actor에서는 하나씩 작업을 실행하도록 해서 low-level의 data race를 막는다. 

 

처음에는 도대체 isolation(뉴진스 하니 아님)이 뭔가 싶었는데 말 그대로 격리 그자체 였다. Actor isolation이라는 것은 Actor와 Actor사이는 완전히 분리되어 있다는 것이고 Task isolation이라는 것은 각각의 Task 사이는 완전히 분리되어있다는 것임!!

 

1️⃣ Task isolation

Task는 동시성 코드에서 작업의 단위이다. 그래서 태스크 내에서는 특정 작업을 순차적으로 처음부터 끝까지 수행하게 된다.

 

WWDC에서 Task는 배로 표현되는데, 각각의 배는 독립된 형태로 분리(혹은 격리)되어 있다. 

 

만약에 A배가 B배로 struct인 파인애플을 넘겨준다고 생각하면 struct인 경우 value 타입이라 값이 복사되기 때문에 배A와 배B는 각각 자신만의 Pineapple을 갖게 된다. 배B가 가진 파인애플의 상태가 변해도 배A가 가진 파인애플은 전혀 영향을 받지 않는다. 파인애플을 공유한다고 해도 각각의 격리가 유지되는 것이다.

 

그런데, class인 닭을 넘겨준다고 생각하면 class는 값이 복사되는 것이 아니라 reference가 복사되는 것이기 때문에, 배 A와 배 B는 같은 레퍼런스를 갖게되고, 결국 같은 닭을 바라보고 있게 된다. 배A에서 해당 레퍼런스에 있는 닭의 상태를 바꾸면 배 B가 접근하는 닭의 상태도 변한다. 이러한 상황을 격리되지 않은 상태, 독립적이지 않은 상태라고 표현하고 이때 data race가 생길 수 있다.

 

그러면 격리된 것 사이에 공유해도 안전한 데이터를 어떻게 구분할 수 있을까 하는 의문이 생길 수가 있다. 위의 예시에서는 파인애플(struct)는 공유해도 안전하고, 닭(chicken)은 공유하면 data race가 생길 수 있다. Swift에서는 Sendable이라는 프로토콜을 제공해서, 해당 프로토콜을 준수하는 타입은 격리된 것 사이에 전송해도 안전한 데이터(전송가능), 준수하지 않은 타입은 전송불가능 데이터로 구분할 수 있게 해준다.

 

Chicken은 Task사이에서 전송될 수 없고, 만약 다른 도메인에 전송하는 코드에 작성하는 경우 컴파일 오류가 난다.

Task는 Success부분에 Sendable프로토콜을 채택한 것만 올 수 있게 되어있다. 결국 Sendable 프로토콜은 서로 다른 격리된 것 사이에 값이 안전하게 이동할 수 있다는 것을 보장해주는 것이다.

 

2️⃣ Actor isolation

Actor는 외부와 isolated(격리)된 형태를 가져서, 공유 자원에 대한 data race를 막는다. 여러 태스크들이 동시에 하나의 Actor에 접근할 수 없고, 그림과 같은 상황의 경우 하나의 Task만 실행되고 나머지 Task들은 대기한다. 

 

WWDC에서 Actor는 섬으로, Task는 배로 표현된다. 배들은 섬에 접근할 때 다른 배가 이미 있으면 섬에 접근하지 못하고 기다려야 하므로, await 키워드를 명시해야한다.

 

위에서 격리된 Task들 사이에 Sendable하지 못한 타입(ex. class인 Chicken)은 전달될 수 없다고 했는데, Task(배)와 Actor(섬) 사이도 똑같다. Actor도 격리된 상태이다. 격리된 섬과 격리된 배 사이에 전달될 수 있는 데이터 유형은 오직 Sendable한 유형이다! (아니면 data race를 만들 수도 있기 때문)

 

await myIsland.addToFlock(myChicken) // error
myChicken = await myIsland.adoptPet() // error

Actor는 Class처럼 참조유형이지만, actor 내의 모든 속성과 코드가 해당 actor 격리되어 data race를 방지하게 된다. 그리고 actor타입은 Sendable을 준수한다.

actor 내의 코드 중 프로퍼티, Task, 전송 불가능한 클로저 등은 모두 해당 액터에 격리된다. 위 코드에서 flock, food, totalSlices, Task {} 는 모두 Island actor에 격리된 형태로 볼 수 있다.

 

그런데 detached된 Task는 actor isolation을 상속받지 않고, 해당 액터 외부에 있는 것으로 간주된다. 그래서 위 코드에서 actor내의 food에 접근하기 위해 await를 표기한 것을 볼 수 있다. 만약 다른 태스크가 이미 Actor에 접근해서 작업을 수행하고 있다면 접근하지 못하고 대기해야 하기 때문이다!!

 

non-isolated? 

actor 내부의 프로퍼티나 함수 등은 actor내부에 격리되어 있지만, 가끔 격리가 필요하지 않거나, 격리하지 않아야 성능이 더 좋을 때가 있다. actor는 여러 작업들을 한 번에 하나씩만 처리하기 때문에 사실상 병렬 컴퓨팅의 장점을 누릴 수 없다는 단점이 있다. 그래서 만약에 actor에서 꼭 실행될 필요가 없는 오래 걸리는 작업의 경우에는 actor내에 격리되어 있기 보다는 actor밖에 위치하고 꼭 actor내의 자원이 필요할때만 actor에 접근할 수 있게 하는 것이 좋다. 

 

actor 내부에 nonisolated된 함수는 사실상 actor 바깥에 격리되어 있는 함수이기 때문에, actor 내의 상태에 접근하기 위해서는 await 키워드를 사용해야 한다. non-isolated된 async 함수는 cooperative pool(WWDC에서는 바다 한가운데로 표현한다.)에서 실행되는데, actor에 접근해서 값을 가지고 나올때도 마찬가지로 sendable한 데이터만 가지고 나올 수 있다. 그래서 sendable 프로토콜을 준수하지 않은 Chicken (class)의 경우에는 컴파일 오류가 난다.

 

nonisolated되지 않아서 actor내부에서 격리된 함수는 액터 내의 상태에 마음껏 접근할 수 있다.

 

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함