면접준비

Actor🕴🏻

2료일 2023. 6. 29. 13:58

- What is Data Race?

  • 두 스레드가 동기화 없이 동일한 객체에 접근하려 할때 ⇒ Data race
  • 이벤트 순서가 프로그램의 정확성에 영향을 미칠때 ⇒ Race Condition
  • ex) Thread별 연산의 실행 순서에 따라 값이 달라질 수 있다! 고로 Data Race≠Race condition
class MyDataManager {
    static let instance = MyDataManager()
    private init() {}

    var data: [String] = []
    func getRandomData() -> String? {
        self.data.append(UUID().uuidString)
        print(Thread.current)
        return data.randomElement()
    }
}

싱글톤으로 만들어봤는데 여러쓰레드가 data 에 접근할수 있기에 data race이다.

그러면 How? thread로부터 안전하게 만들어줄수 있으까요?

class MyDataManager {
    static let instance = MyDataManager()
    private init() {}

    var data: [String] = []
    private let lock = DispatchQueue(label: "MyDataManager")
    func getRandomData(completionHandler: @escaping (_ title: String?) -> ()) {
        lock.async {
            self.data.append(UUID().uuidString)
            print(Thread.current)
            completionHandler(self.data.randomElement())
        }
    }
}

 

다른 함수가 끝날때까지 기다리도록! completion handler

하지만 Actor가 등장하면서 컴파일러단위에서 검사가 가능해졌다.!! 대박

Actor🕴🏻🕴🏻

:클래스와 같은 참조타입이지만 스레드 안정성을 보장해준다.(data race가 안생기도록 보장해준다.)

  • 한번에 하나의 작업만 변경 가능한 상태에 접근할 수 있도록 허용해준다.
  • 커스텀 큐 없이 actor에 요청한 데이터 패치 순서보장해줌!!
  • 상속은 불가.
  • Actor에 들어가기전 항상 await keyword 사용 왜? isolated되어있어서
  • 모든 Actor는 Cooperative thread pool에서 동작.
  • Actor내에 정의된 stored, computed instace 프로퍼티, 인스턴스 메서드 모두 actor-isolated결국 actor는 self영역 외에서 해당 actor의 shared mutable state를 접근하려할때 not isolated-state로 여기고 에러발생
  • ⇒ await가 붙은것은 바로 실행하는 것이 아닌 차후에 실행시켜달라고 해당 Actor에 보내는것과 같음. 그러면 Actor가 가지고 있다가 다른 작업 끝내고 나서 하나씩 처리하도록 하기에 data race X
  • 이 actor안에 보면 비동기로 되어있지 않다.하지만 밖에서 부를때는 비동기로 되어있다.
actor SunhoBank {
    let accountNumber: Int
    var balance: Double

    init(accountNumber: Int, firistDeposit: Double) {
        self.accountNumber = accountNumber
        self.balance = firstDeposit
        }
       }

extension SunhoBank {
    func transfer(amount: Double, to other: SunhoBank) throws {
        if amount > self.balance {
            throw Error
            }
        print("\(amount)를 \(accountNumber)로 부터 \(other.accountNumber)로 옮깁니다.")
        self.balance = balance - amount
        other.balance = other.balance + amount
    }
}

눈으로보며 이해를 해보자.

class였다면 balance라는 변수에 동시 접근하기에 data race이다. 하지만 actor기에 other.balance 변경에서 에러가 생긴다. 왜? 만약 A와 B가 C의 계좌로 돈을 보낸다고 가정을 해보자.모두 만원을 가지고 있다고 가정

  1. A는 other.balance를 통해 어?C는만원을 가지고 있구낭
  2. B도 마찬가지로 other.balance를 통해 어? C는 만원가지구 있네?헤헤
  3. B는 아이스크림 내기에서 져서 C에게 5500원 보내주어야해서 입금해주자. self.balance = 10000-5500, other.balance = 10000+5500 =>C는 15500원
  4. A는 기분이 좋아 C에게 500원 보내준다. 그러면 other.balance = 10000+500
  5. 에? 그러면 결국 마지막에 실행된 놈때문에 C의 계좌는 10500원..뭐야..

그래서 저 코드를 잘보면 Actor 마지막쪽에서 에러가 뜬다.

actor-isolated propoerty "balance"

Actor란놈은 고립시킴으로써 안에 mutable한놈들을 보호한다!!

actor내에 정의된 stored, computed instance properties, instance methods, instace subscripts는 모두 actor-isolated

=> actor-isolated된놈들은 연결되거나 격리된 특정 actor내에서 접근할수있는데 바로 self. 결국self를 통해서만 접근


Actor에서 다른 Actor에 접근 하는 것을 Cross-Actor Reference라 하는데 단 두가지 경우 밖에 안된다.

  1. 접근하는 프로퍼티가 immutable할 때. => 상수에 접근하는 것은 self로 막지 않고 누구나 할수 있다.
    • 위에서 balacne 에 접근할때는 에러가 떳지만 other.accountNumber는 에러가 생기지 안흔다. 왜? 저건 let.
    • actor가 정의된 동일할 모듈의 어느곳에서 항상 허용인데(한번 초기화 되면 해당 상태는 수정할수 없기 때문에)
    • 다른 모듈에서는 허용이 안된다!!!
  2. Cross-Actor Reference가 비동기 함수 안에서 등장
    1. Async 함수 안에 await가 붙어있으면 차후에 시켜달라고 actor한테 알려주는 것. actor가 가지고 있다가 작업이 끝나면 하나씩 처리하도록 할 수 있음. 동시적 객체 접근하게 될 가능성이 없어짐
await other.deposit(amount: amount)
extension SunhoBank {
    func deposit(amount : Double)  {
        assert(amount >= 0)
        balance = balance + amount
    }
}

이런식으로 마지막에 other.balance를 빼주고 추가하게 된다면 transfer함수는 other.deposit 메서드를 호출는데 await가 있어서 transfer를 호출한 SunhoBank는 other에서 작업이 완료될때까지 기다림.
이렇게 된다면 A가 C에게 5천원 입금한다면 내부 await other.deposit에서 C에게 너 depoist해라 알려주는거, 그러면 A는suspend되며 멈춘다. C는 원래 지할일하다가 A로부터 deposit을 받고나서 하던일을 마치고나서 그걸 한다. 완료되면 C는 다른 일다시 하고 A는 A의 작업흐름으로 돌아간다.

그런데? 왜 저기 deposit을 호출할때는 await인데 deposit은 async가 아니지? 동기잖아?


 Actor는 어떠한 순서로 들어온 작업들을 처리할까??라는 궁금증이 생겼다!! 나만그래?

런타임에서 가장 우선순위 높은 작업을 선택해서 실행 .이를 통해 효율적인 스케쥴링 , 리소스 활용.

선입선출? ㄴㄴ~ 우선순위를 줄 수 있다. 런타임은 우선순위가 높은 항목을 우선순위가 낮은 항목들보다 먼저 큐의 앞으로 이동하도록 선택.

 

Actor Hopping은 한 Actor에서 다른 Actor로 스레드가 이동하는 과정입니다. 예를 들어, feedActor에서 databaseActor로 데이터를 저장하는 경우를 생각해보자.

actor DatabaseActor {
    func save(data: String) async {
        print("데이터 저장 중: \(data)")
    }
}

actor FeedActor {
    let database = DatabaseActor()
    
    func fetchAndSave() async {
        let data = "새로운 피드 데이터"
        await database.save(data: data)
    }
}

코드에서 fetchAndSave()가 실행되면 await database.save(data: data)에서 suspend되면서 FeedActor → DatabaseActor로 Actor Hopping이 발생한다. 스레드가 다른 Actor에 대한 작업을 시작하기 위해 한 Actor에 대한 작업을 일시 중지할 때 ⇒ Actor Hopping

Actor Hopping 문제점:

  • 하나의 Actor에서 suspend되면 실행 중인 코드 컨텍스트가 없어질 수 있음
  • 너무 자주 Actor Hopping이 발생하면 성능 저하 가능
  • MainActor에서의 Actor Hopping 주의 (UI 처리를 방해할 가능성이 있음

아직 database actor는 사용된적이 없다고 가정. database actor에 접근한다.

쓰레드는 feed actor > databaseActor로 hopping!!

왜? ex) sports 피드에서 await datase.save를 햇다쳐 await이기에 S1작업은 suspend된다. 그리고 나서 database D1작업

thread가 블록되지 않기에 다른 쓰레드가 필요없다.

자 ! 그렇다면 다른 actor에서 DB에 저장하려고 하면 어떻게 될까?

안타깝게도 아직 첫번째 work item이 끝나지 않았다. weather에서 DB에 저장하려고 하면 새로운 work item 생성(D2). W1= await database.save

자 시간이 흐르고 나서 D1요청이 끝나면 D1work item제거.

그리구 후 작업은 위에서 설명한 순서대로 이루어진다


Actor Reentrancy? 이것덕에 actor는 시스템이 work우선순위잘정해줄수있다.

  1. D1 = Database actor에서 어떤 작업에 대해 await를 하면서 이 database actor는 suspend!!!
  2. 쓰레드가 자유로워졌기에 sports feed로 actor hopping

3. sports feed actor 동작을 완료하였고 여기서 database actor에게 저장해달라고 요청

4. database actor는 동작하지 않고 uncontended상태이기에 D1이 있음에도 쓰레드는 database actor를 hopping할수 있다.

5. save하기위해 D2작업생성. = >Actor에대해 새로운작업이 그전작업이 suspend되는동앉 진행가능

D2는 D1이후에 생겻어도 D1보다 먼저 실행될수 있다.

developer가 여러 task가 Actor에접근할때 어떤 순서대로 나올지 알수 있나?

액터는 작업들 간에 자체적인 스케줄링을 수행하며, 정확한 순서를 보장해주지는 않습니다.

하지만 의존성과 동기화 메커니즘을 활용하여 작업들 간의 상호작용과 실행 순서를 조절할 수 있습니다.


Main Actor

main thread는 알다시피 UI를 처리하기에 여기에는 너무 많은 작업이 있으면 안된다. 그래서 디폴트는 global thread인데 main actor만 mainthread에서 작업을 하게 된다.


Actor에서 만나는 오류들 정리

Main actor-isolated property 'transcript' can not be referenced from a non-isolated context; this is an error in Swift 6

Actor의 모든 메소드는 isolated하다. 그런데 non-isolation한 곳에서 isolated한것을 부르면 암시적으로 비동기 호출

=> 그러면 해당하는 것을 비동기적으로 하면 되지않을까?

=> 아니면 Task await으로 묶을까?

주의

1. Actor에서 await호출 이후 내부 상태 가정 금지!!

액터 도중 suspend걸리면 그 전실행중코드없어짐

2. Actor에서 많은 양의 작업 진행 X

@globalActor

: 특정 액터 사용을 글로벌 스레드를 통해 사용할수 있게 하는 방법.

참고한 감사한 자료들입니다.

https://zeddios.tistory.com/1303

[Swift ) Actor (2) - Actor isolation

📖 Actor (1) 읽으러가기 # Actor 특징 복습 📝 - Actor는 그냥 Swift의 새로운 타입임. 클래스와 가장 유사. - Swift의 다른 모든 타입들과 똑같이 프로퍼티, 메소드, 이니셜라이저, subscripts 등을 가질 수

zeddios.tistory.com](https://zeddios.tistory.com/1303)