Actor와 Sendable에 대한 오해

저는 actor와 sendable의 차이를 이해하기에 “관점의 차이”가 가장 도움됐습니다.

actor와 Sendable의 관점 차이
  • actor : 여러 스레드에서 안전하게 접근할 수 있도록 줄을 세워주는 타입 (= 접근 통제)
  • Sendable : 다른 스레드로 안전하게 전달할 수 있음으로 보장하는 프로토콜 (= 안전 인증)

즉 actor는 여러 스레드가 접근하는 데에서 발생하는 문제를 방지하고자하는 “접근에서의 안전 관점”이고, sendable은 객체의 참조나 값이 스레드 간에 이리저리 전달되는 데에서 발생하는 문제를 방지하고자하는 “전달에서의 안전 관점”입니다.

actor

actor는 한번에 하나의 스레드만 접근할 수 있도록 serial queue로 제한하고 있기 때문에 동시에 다른 스레드가 접근함으로써 내부 상태가 바뀌어버리는 문제를 막을 수 있고, serial queue로 접근이 제한되기 때문에 자동으로 Sendable 합니다.

다른 스레드에 전달함으로써 여러 스레드가 참조해도 안전하다는거죠.

Sendable

Sendable은 스레드 간에 전달됨으로써 여러 스레드가 참조하고 있어도 내부 상태가 불변(immutable)이거나 내부적으로 동기화하기 때문에 race condition이 발생하지 않아 안전합니다.

race condition/data race

race condition : 공유된 데이터 하나를 놓고 여러 스레드가 경합을 벌인다는 의미로, 여러 스레드가 동시에 하나의 공유 자원에 접근하여 자원을 write 작업 하려는 현상입니다. 어떤 스레드가 먼저 도착해서 데이터를 읽고 쓸지 순서를 예측할 수 없기 때문에 예측 불가능한 버그가 발생합니다.

동기화? 동기화(Synchronization)라는 키워드는 컴퓨터 과학에서 다의어입니다!

  1. 데이터 동기화 (Data Synchronization) : 원격 서버의 데이터와 로컬 데이터를 같은 상태로 일치시키는 것
  2. 동시성 동기화 (Concurrenty Synchronization) : 여러 스레드가 공유 자원에 접근하는 순서를 제어하는 것
  • 예시: actor, NSLock
만약 굳이 내부 상태가 모두 불변하고 변경 가능한 상태가 추가될 여지가 없어 여러 스레드가 접근해도 되거나 race condition이 발생할 일이 없음에도 actor로 정의하면 어떤 일이 발생할까요..?

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

값 타입은 전달될 때 참조가 아니라 복사됨으로써 전달되기 때문에 전달된 값의 내부 상태를 변경해도 다른 값에 영향을 주지 않죠? 그래서 값 타입은 암시적으로 Sendable 합니다. 그러나 값 타입도 Sendable을 채택해야 할 때가 있습니다. 바로 명시적 채택을 통한 검사가 필요한 때입니다. 값 타입인 구조체의 내부 프로퍼티 중 참조 타입이 있다면, 값 타입으로 감싸져 있지만 결국 해당 값은 참조 타입이기 때문에 Sendable 하다고 할 수 없습니다. 이때 명시적으로 Sendable을 채택함으로써 해당 참조 타입이 Sendable 한지 컴파일러가 검사하기 때문에 런타임에서 마주할 예상치 못한 버그를 방지할 수 있습니다.


이렇게 두 개념에 대한 명확한 차이를 알면 실무에서 바로바로 계산하면서 사용하는 것이 어려울지언정, 버그나 에러가 발생했을 때 보다 빠르게 문제를 파악할 수 있겠죠? ☺️