Swift Concurrency 9편 Data Race 2부 - Actor reentrancy

김재형·2024년 7월 3일
0
post-custom-banner

들어가기에 앞서

SwiftConcurrency 1편 부터 이어지는 내용입니다. 꼭 1편부터 보고 와주시길 바랍니다.

Reentrancy

Reentrancy 란 동일한 함수가 이미 호출중 일경우 다시 호출될수 있는 것을 말합니다.

Actor reentrancy

즉 다시말해 Actor reentrancyactor 의 메서드가 다른 작업을 수행할때 (중일때)
다시 호출될수 있음을 말하죠.

import Foundation

actor MyActor {
    private var value = 0
    
    func increment() {
        value += 1
        print("Incremented value to \(value)")
    }
    
    func performAsyncWork() async {
        print("엑터 시작")
        await Task.sleep(for: .seconds(1))  // 1초 대기
        print("엑터 끝")
    }
    
    func performWork() async {
        print("performWork 시작")
        await performAsyncWork()
        print("performWork 끝")
    }
}

let myActor = MyActor()

Task {
    await myActor.performWork()
}

Task {
    await myActor.increment()
}

하지만 만약 저장소와 원격 저장소를 동기화 하는 경우일 떄는 어떨까요?

import Foundation

actor UserStore {
    private var store: [String: String] = [:] // 내부 저장소: 아이디 -> 이름
    
    // 원격 저장소에서 데이터를 가져오는 메서드 (모의 구현)
    private func fetchFromRemoteStore(id: String) async -> String {
        // 원격 서버와 통신하는 시간이 걸림을 모의
		await Task.sleep(for: .seconds(1)) // 1초 대기
        return "RemoteUserName" // 
    }
    
    // 동기화 메서드
    func remoteSynchronize(id: String) async -> String {
        if let name = store[id] {
            return name
        }
        
        // 원격 저장소에서 사용자 정보를 가져와 동기화
        let remoteName = await fetchFromRemoteStore(id: id)
        store[id] = remoteName
        return remoteName
    }
}
  • A 쓰레드가 B 쓰레드와 동일한 아이디로 remoteSynchronize 를 호출하게 됩니다.

  • A가 내부 저장소를 확인후 -> 해당 아이디의 정보가 없음을 확인하겠지요

  • A가 원격 저장소에서 데이터를 가져오는 동안에 await 키워드를 만나 값이 오기까지 대기합니다.

  • B가 실행되어 동일한 아이디로 내부 저장소를 확인하고 -> 데이터가 없음을 확인합니다.

  • B도 원격 저장소에서 데이터를 가져오려고 시도합니다 즉 await 키워드를 만나 값이 오기까지 대기합니다.

  • A가 원격 저장소에서 데이터를 받아와 내부 저장소에 저장합니다.

  • B도 마찬가지로 내부 저장소에 저장하겠죠.

문제점이 무엇일까요?

동일한 요청이 발생하는구나!
이는 즉 중간에 바뀐 정보가 있다고 하면 다른 결과값을 받아오게 되겠어요!
이 상황을 Actor reentrancy 라고 합니다.

Actor isolation

actor가 멀티스레드 환경에서 데이터 경쟁을 방지하기 위한 개념으로,
actor 내의 가변 상태는 외부에서 직접 접근할 수 없으며,
모든 접근은 actor 인스턴스 메서드를 통해 이루어지게 해요!
이는 actor가 내부적으로 serial queue(직렬화 큐)를 사용하여 동작하기 때문에,
동시성 문제를 해결할 수 있어요!

접근 하는 법은?

self를 통한 접근 방법 (내부)

actor 상태에 접근 하려면 self 키워드를 통해 접근이 가능해요!
클래스 혹은 구조체 self 와 매우 유사하죠.

actor MyActor {
    private var count: Int = 0
    
    func increment() {
        self.count += 1
    }
    
    func getCount() -> Int {
        return self.count
    }
}

cross-actor refrence ( 교차 Actor 참조 )

만약 Actor 외부에서 actor 의 상태나 메서드에 접근 해야 한다면,
비동기 메서드를 호출하여 접근해야 합니다. await 키워드 함께요!

/*
 func increment() async {
        self.count += 1
    }
*/

let myActor = MyActor()

Task {
    await myActor.increment()
    let currentCount = await myActor.getCount()
    print("Current count: \(currentCount)")
}

isolated

isolated 키워드는 특정 메서드가 actor의 격리된 상태 에서 호출됨을 명시하는 키워드 에요!
즉 안전하게 actor의 상태에 접근할 수 있게 됩니다.
주로 actor의 메서드 매개변수로 사용되어, 해당 메서드가 특정
actor 인스턴스의 격리된 상태에서 호출된다는 것을 명시합니다
즉, 안전성을 높이고, 동시성 문제를 관리할 수 있게 되죠!

actor MyActor {
    private var count: Int = 0
    
    func increment() {
        self.count += 1
    }
    
    func getCount() -> Int {
        return self.count
    }
    
    // `isolated` 키워드를 사용한 메서드
    func resetCount(isolatedTo actor: isolated MyActor) {
        actor.count = 0
    }
     // `isolated` 키워드를 사용하지 않은 메서드
    func resetCount(actor: MyActor) {
        actor.count = 0 // 오류: 'count'에 접근할 수 없습니다.
    }
}

let myActor = MyActor()

Task {
    await myActor.increment()
    let currentCount = await myActor.getCount()
    print("Current count: \(currentCount)")
    
    // `isolated` 키워드를 사용한 메서드 호출
    await myActor.resetCount(isolatedTo: myActor)
    let resetCount = await myActor.getCount()
    print("Count after reset: \(resetCount)")
}

isolated 키워드를 사용하지 않는 경우도 작성해 보았습니다.
하지만 위와 같은 에러가 발생합니다. 이유가 무엇일까요?

actor 내부의 상태는 외부에서 직접 접근하거나 수정하는 것을 허용하지 않아요...
isolated 키워드를 명시하여. actor의 격리된 상태에서 안전하게 상태를 수정할 수 있게 되죠.

이번편을 마무리 하며...

자 이번에도 거침없이 무지막지한 내용을 가지고 와봤습니다.
... 하지만 아직도 끝이 아니죠 ㅎㅎ 다음 시간에는 nonisolated 와 드디어 저번에 예고했던
Sendable 편으로 돌아와 보겠습니다. 고생하셨습니다~

profile
IOS 개발자 새싹이
post-custom-banner

0개의 댓글