Concurrency - Actor

Marble·2024년 12월 28일

Concurrency

목록 보기
5/10

동시성 프로그래밍(Concurrent Programming)을 할 때는 Data Race가 발생하지 않도록 해야합니다.

Data Race

데이터 레이스란 멀티 스레드/프로세스 환경에서 일어나는 오류로 여러 스레드/프로세스가 공유 자원에 동시에 접근하려 할 때, 일어나는 경쟁 상황을 의미합니다.

예시로 위 이미지는 Counter 클래스에 있는 number를 1씩 증가하는 함수를 호출하는 코드입니다. DispatchQueue.global().async를 사용해서 비동기로 함수를 호출하기 때문에 counter.increment() 함수는 두 개의 스레드에서 동시에 실행됩니다. increment() 함수를 2번 호출했기 때문에 number의 값은 2가 되어야하지만 두 함수가 서로 다른 스레드에서 실행될 때 number의 값은 0이기 때문에 두 함수 모두 number의 값을 1로 증가하게 되고 결과값은 1이 됩니다. 이러한 문제를 해결하기 위해 Actor를 사용할 수 있습니다.

Actor

Actor는 내부 상태에 대한 접근을 직렬화하여, 동시에 여러 스레드가 접근하더라도 안전하게 상태를 관리할 수 있도록 합니다. Actor는 actor 키워드를 사용하여 정의하며, 내부에 상태를 저장할 수 있는 프로퍼티와 메서드를 가질 수 있습니다. 예를 들어, 다음과 같은 코드로 Actor를 정의할 수 있습니다.

actor Counter {
	var number = 0
}

기존에 작성한 class명령어 자리에 actor를 작성하면 끝입니다. 메서드를 호출할 때 class와 비슷하지만 비동기 함수를 호출하듯 await 명령어를 앞에 붙여줘야 합니다. 위의 이미지를 actor로 정의하고 호출하면 아래 이미지처럼 에러가 발생하지 않습니다.

만약 다른 함수 안에서 호출할 경우 함수를 정의할 때 async 키워드를 달아서 비동기 함수임을 명시해줘야 합니다.

func someFunction() async {
	let counter = Counter()
	await counter.increment()
}

Actor Isolation

actor 내의 프로퍼티는 기본적으로 private이며, 외부에서 직접 접근할 수 없습니다. 이는 내부 상태가 외부의 영향을 받지 않도록 보장합니다. 즉, 모든 상태 변경은 actor 내의 메서드를 통해 이루어져야 하므로, 메서드 호출 시 스레드 안전성을 보장합니다.

위에서 언급했듯이 메서드를 사용할 땐 비동기로 actor에 접근해야하며, actor의 상태에 접근할 때는 해당 메서드가 완료될 때까지 다른 스레드가 접근할 수 없습니다. 이는 actor가 하나의 스레드에서만 상태를 변경하도록 강제하기 때문이며 이를 통해 데이터 레이스를 방지합니다.

Actor와 함께 알아두어야 할 개념으로는 Sendable과 cross-actor reference가 있습니다.

Sendable

Sendable은 Swift의 프로토콜로, 객체가 안전하게 다른 스레드나 actor 간에 전송될 수 있음을 나타냅니다. Sendable 프로토콜을 준수하는 객체는 스레드 간에 안전하게 공유될 수 있으며, 이 객체는 불변(immutable) 상태여야 합니다.

Sendable은 다음과 같은 특징이 있습니다.

  • 불변성: Sendable을 준수하는 객체는 상태를 변경할 수 없거나, 변경이 불가능한 방식으로 접근해야 합니다. 이는 데이터 레이스를 방지합니다.
  • 타입 안전성: Swift 컴파일러는 Sendable 프로토콜을 준수하지 않는 타입에 대해 경고를 발생시켜, 개발자가 안전하지 않은 코드 작성을 방지할 수 있도록 도와줍니다.

Cross actor reference

Cross actor reference는 서로 다른 actor 간의 참조를 의미합니다. Swift에서는 서로 다른 actor 간에 데이터를 안전하게 공유하기 위해 다음과 같은 규칙을 따릅니다.

  1. 비동기 메서드 호출
    서로 다른 actor에 속한 인스턴스에 접근할 때는 반드시 비동기 메서드를 사용해야 합니다. 즉, 다른 actor의 메서드를 호출할 때는 await 키워드를 사용해야 합니다. 이러한 비동기 호출은 actor 간의 상태 변경을 직렬화하여 데이터 레이스를 방지합니다.
  2. Sendable 요구 사항
    cross actor reference를 사용하는 객체는 Sendable 프로토콜을 준수해야 합니다. 이는 해당 객체가 다른 스레드나 actor 간에 안전하게 전송될 수 있도록 보장합니다. Sendable을 준수하는 객체는 불변 상태를 유지하거나, 상태 변경을 안전하게 관리할 수 있어야 합니다.
  3. 직접적인 상태 변경 금지
    한 actor가 다른 actor의 상태를 직접적으로 변경할 수 없습니다. 대신, 다른 actor의 메서드를 통해서만 상태를 변경할 수 있습니다. 이는 actor의 안전성을 보장하기 위한 중요한 규칙입니다. 예를 들어, actorA의 상태를 변경하려면 actorB에서 actorA의 메서드를 호출해야 하며, 이때 await를 사용해야 합니다.
  4. 캡슐화
    actor는 자신의 상태를 캡슐화합니다. 이는 내부 상태가 외부에서 직접 접근할 수 없음을 의미하며, 다른 actor와의 상호작용은 메서드를 통해서만 이루어져야 합니다. 이러한 캡슐화는 객체 지향 프로그래밍의 중요한 원칙인 정보 은닉을 따릅니다.
  5. 에러 처리
    서로 다른 actor 간의 메서드 호출에서 발생할 수 있는 에러는 비동기적으로 처리해야 합니다. 즉, 호출하는 쪽에서 try와 await를 사용하여 에러를 처리해야 합니다.
profile
개발자가 되고 싶은 공돌이

0개의 댓글