Actor와 Sendable 이해하기

☝🏻 Actor와 Sendable에 대해 공부한 내용을 작성한 글입니다.

Actor한번에 하나의 스레드만 접근할 수 있도록 Serial Queue로 제한하고 있어, 동시에 다른 스레드가 접근함으로써 내부 상태가 바뀌어버리는 문제를 막을 수 있습니다.
그리고 접근을 통제하기 때문에 자동으로 Sendable 합니다.
다른 스레드에 전달함으로써 여러 스레드가 참조해도 안전하다는거죠!

Sendable은 스레드 간에 전달되면서 하나의 자원을 여러 스레드가 참조해도, 내부 상태가 불변(immutable)하거나 내부적으로 동기화하기 때문에 race condition이 발생하지 않아 안전합니다.

용어 정리 🧹
  • Race Condition : 공유된 데이터 하나를 놓고 여러 스레드가 경합을 벌이는 현상
    • 하나의 공유자원에 여러 스레드가 동시에 접근해 write 작업을 하려고 할 때 발생합니다.
    • 어떤 스레드가 먼저 도착해서 데이터를 읽고 쓸지 순서를 예측할 수 없기 대문에 예층 불가능한 버그가 발생할 수 있습니다.
  • (동시성)동기화 : 여러 스레드가 공유 자원에 접근하는 순서를 제어하는 것

🤔 Sendable 채택만이 필요할 때 Actor로 정의해버리면 어떤 현상이 발생하죠?

Actor와 Sendable을 잘못 이해했을 때 가졌던 궁금증입니다..ㅎ

만약 굳이 내부 상태가 모두 불변하고 변경 가능한 상태가 추가될 여지가 없어 여러 스레드가 접근해도 되거나 race condition이 발생할 일이 없음에도, 즉 Sendable 채택이 필요할 때 Actor 타입으로 정의해버리면 어떤 현상이 발생할까요?

Actor 특성 상 내부 접근 동작을 직렬로 수행하고 await를 통해 하나의 스레드에서 접근하면 다른 스레드는 해당 스레드의 작업이 일시중단되거나 종료되기를 기다려야 하기 때문에, 불필요한 직렬화로 성능 병목이 발생할 수 있습니다. 교착 상태(Deadlock) 위험이 증가하는거죠.
이런 경우, Sendable을 채택함으로써 데이터 전달에 대한 안전만을 보장하는 것이 적절합니다.

🤔 값타입은 자동으로 Sendable이라면서 왜 채택하죠?

값 타입은 전달될 때 참조가 아니라 복사됨으로써 전달되기 때문에 전달된 값의 내부 상태를 변경해도 다른 값에 영향을 주지 않습니다. 그래서 값 타입은 암시적으로 Sendable 합니다.

그러나 값 타입도 Sendable을 채택해야 할 때가 있습니다.
바로 ✨명시적 채택✨을 통한 검사가 필요한 때입니다.

값 타입인 구조체의 내부 프로퍼티 중 참조 타입이 있다면, 값 타입으로 감싸져 있지만 결국 해당 값은 참조 타입이기 때문에 Sendable 하다고 할 수 없습니다. 이때 명시적으로 Sendable을 채택함으로써 해당 참조 타입이 Sendable 한지 컴파일러가 검사하기 때문에 런타임에서 마주할 예상치 못한 버그를 방지할 수 있습니다.


마지막으로 Actor와 Sendable의 차이를 간단명료하게 정리하기 위해 저의 이해를 도왔던 키워드를 소개하고자 합니다.

👀 Actor와 Sendable의 관점 차이

Actor는 여러 스레드에서 안전하게 접근할 수 있도록 줄을 세워주는 타입이기 때문에 “접근을 통제한다”라고 할 수 있고, Sendable은 다른 스레드로 안전하게 전달할 수 있음으로 보장하는 프로토콜이기 때문에 “안전함을 인증한다”라고 할 수 있습니다.

즉, 어떤 관점에서 안전한지에 차이가 있다는거죠!

  • Actor : 접근에서의 안전 관점
  • Sendable : 전달에서의 안전 관점