감자주먹밥

[Swift] Swift Concurrency - Actor 본문

IOS/Swift

[Swift] Swift Concurrency - Actor

JustHm 2023. 3. 14. 20:14
728x90

비동기 작업을 하면서 발생할 수 있는 문제는 Data race 이 있다. Swift Concurrency는 이를 방지하기 위해 컴파일 단에서 오류를 내는 식으로 이를 방지하는데, Data race를 피하면서 여러 task가 하나의 자원을 공유하면서 쓰려면 어떻게 해야할까?

GCD에서도 알아봤지만, data race는 여러 스레드가 하나의 자원에 동시에 접근할 때 발생할 수 있다.
읽기만 하면 문제는 없지만, 값을 변경하려고 할 때 중간에 생각했던 값이 아닌 바뀐값이 나올 수 있기 때문이다.
Mutex, Semaphore를 사용해서 한번에 하나씩만 접근할 수 있게해 해결할 수 있는데, Swift Concurrency의 Actor도 한 번에 하나의 Task만 접근할 수 있게 하여 data race를 해결 할 수 있다.

Actor

Actor는 class와 비슷하다. reference type이며 만드는 방법도 비슷하다.

actor Person {
    let name = "AAA"
    var age: Int = 9
    
    init(age: Int) {
        self.age = age
    }
    func addAge(year: Int) {
        age += year
    }
}

 

Swift Concurrency에 data race를 피하기 위해 사용되는 만큼 특징이 있다.

  • 공유되는 가변 상태를 표현한다. (shared mutable state)
  • 한번에 하나의 task만 mutable state에 접근할 수 있도록 허락한다.
  • 상속을 지원하지 않고, 암시적으로 Sendable 하다.
  • 직렬화를 제공하고, 독립적이다.
let aaa = Person(age: 0)
Task {
    print("Name: \(aaa.name)")
    print("Age: \(await aaa.age)")
    await aaa.addAge(year: 12)
    print("After Add Age: \(await aaa.age)")
}

Actor는 한 번에 하나의 task만 접근할 수 있도록 허락하는 특징이 있어 값에 접근하거나, 메서드를 사용할 때 await 키워드를 써야한다.

상수의 경우 변하지 않으니 어느곳어서 접근해도 안전하기 때문에 그냥 접근 가능하다.

Actor Isolation

Actor는 가지고 있는 변경 가능한 상태를 고립시켜 보호한다. 이 원칙에 따라 Actor는 self로 자기 자신의 프로퍼티에 접근 할 수 있다.

만약 다른 Actor를 받아 프로퍼티에 접근하려 하면 에러가 뜬다.

에러를 보면 actor-isolated 프로퍼티인 other의 value를 non-isolated actor 인스턴스에서 변경할 수 없다는 것.

Actor에 정의된 멤버들은 모두 actor-isolated 상태다. actor-isolated 상태인 것들은 위에서도 말 했듯이, self를 통해서만 접근할 수 있다. (내부에서만 변경이 가능하다는 것)

에러에서 말하는 non-isolated는 other.value를 말하는 것, 자기자신의 value가 아닌 다른것의 value를 가져왔기 때문에 other.value는 non-isolated다.

요약하면, non-isolated는 actor-isolated에 동기적으로 접근이 불가능.

Cross-Actor Reference

Actor 외부에서 actor-isolated를 참조하는것을 Cross-Actor Reference라고 한다. 두 가지 방법이 있다.

Immutable state

actor Person {
    let name = "AAA"
    var age: Int = 9
}
let aaa = Person()
Task {
    print(aaa.name)
}

let과 같은 Immutable state 어디서나 접근이 가능하다. 상수의 값은 변하지 않고, 변경도 불가능하기 때문에 data race문제가 발생할 일이 없다. 그렇기 때문에 immutable state 접근은 가능한것이다.

Call Asynchronous Function

비동기 함수 내 호출을 하게 되면, 가능할 때 해당 작업을 실행해달라는 메시지를 보내게 된다. 이 메시지는 Actor가 가지고 있다가 순차적으로 메시지를 처리한다. 비동기 함수 내 호출은 메시지가 처리 될 때 까지 일시중단 될 수 있다. 이런 방법으로 동시성이 없기 때문에 Data race가 발생하지 않는다.

func combineOtherCounter(otherValue: Int, other: Counter) async {
    add(value: otherValue)
    await other.add(value: value)
}

현재 카운터의 값과 다른 카운터의 값을 합치는 코드를 작성했다.

other의 add가 메시지가 되어 other는 메시지를 받는다. other는 받은 메시지를 순차적으로 실행한다. 최종적으로 value를 업데이트한다.

이 두 방법으로 Cross-Actor Reference를 할 수 있다.

Keyword isolated, nonisolated

isolated

//isolated keyword 미사용
func combineOtherCounter(other: isolated Counter) async {
    value += (await other.value)
}
//isolated keyword 사용
func combineOtherCounter(other: isolated Counter) {
    value += other.value
}

Actor 외부에서 actor-isolated를 접근하려면 Cross-Actor-Reference 방법을 사용했어야 했는데, parameter에 isolated 키워드를 붙여주면, 메서드가 actor-isolated 상태가 되어 그냥 사용할 수 있게 된다.

nonisolated

actor Person {
    let name = "AAA"
    var introduce: String {
        return "my name is \(name)"
    }
}
let aaa = Person()
Task {
    print(await aaa.introduce)
}

위 코드의 introduce는 immutable value를 포함한 read-only computed property다. immutableValue를 사용해 연산하기 때문에 data race가 발생할 일이 없지만, 선언자체가 mutable로 되어있기 때문에 await를 사용해 값을 얻어온다.

nonisolated 키워드를 사용하여 non-isolated 상태로 만들어 사용할 수 있다.

actor Person {
    let name = "AAA"
    nonisolated var introduce: String {
        return "my name is \(name)"
    }
}
let aaa = Person()
Task {
    print(aaa.introduce)
}

메서드, 프로퍼티 둘 다 적용이 가능하고, nonisolated 키워드가 붙으면 동기적으로 접근을 할 수 있다.

Sendable Type

----

Main Actor

MainActor는 concurrency의 Actors API 중 하나로, main 스레드에서 동작을 보장하는 Actor

@globalActor final actor MainActor

Swift Concurrency를 적용하며 UI작업을 해야할 때 Main Actor를 사용했다. 이는 main dispatch Queue에 동기화 작업을 수행할 수 있게 하는 global actor다.

이전에 다른 스레드 UI작업 시 DispatchQueue.main을 사용해서 해결했던 것과 비슷하다. 

@MainActor 어노테이션을 프로퍼티, 메서드, 클래스 등에 붙여 사용할 수 있다.

@MainActor
func someFunction() {}

MainActor 어노테이션을 붙이면 MainActor의 actor-isolated 상태가 되고 이들을 Actor외부에서 비동기적으로 전달한다.

MainActor가 붙은 클래스에 특정 메서드나 프로퍼티를 non-isolated로 하고 싶다면 nonisolated키워드를 붙이면 된다.


https://seokyoungg.tistory.com/41#3.%20Actor%20Reetrancy

 

[iOS] 동시성 프로그래밍(12) - Actors

Data Race는 shared mutable data에 대해 여러 Thread에서 동시에 접근하여 발생한다. 동시성 환경에서 자주 발생하는 문제 중 하나이지만, Debug 하기는 어렵다. class Counter { var value = 0 func increment() -> Int { va

seokyoungg.tistory.com

https://zeddios.tistory.com/1290

 

Swift ) Actor (1)

안녕하세요 :) Zedd입니다. WWDC 21 ) What‘s new in Swift 에서도 잠깐 본 내용인데, Actor에 대해서 공부. # 다중 쓰레드 시스템에서 제대로 작동하지 않는 코드 WWDC 21 ) What‘s new in Swift 에서 본 예제. class

zeddios.tistory.com

 

728x90

'IOS > Swift' 카테고리의 다른 글

[Swift] XML Parser 사용하기  (0) 2023.04.05
[Swift] Swift Concurrency - Task  (0) 2023.03.06
[Swift] Swift Concurrency - async/await  (0) 2023.03.02
[Swift] 동시성 프로그래밍 GCD, Operation  (0) 2023.02.25
Xcode 단축키 모음  (2) 2022.01.13
Comments