[Swift] 모던 컨커런시 Modern Concurrency

jae·2024년 12월 15일

swift

목록 보기
12/14

현대적인 동시에 하는 작업인데
도대체 뭐가 현대적인 병행 작업인걸까

컨커런시

동시성이라는 Concurrency 에서 알수 있다싶이 동시에 하는 작업 즉 병렬로 함께 하는 작업이다
흔히 비동기라고 네트워크 요청을 다른 스레드에서 요청하고 메인스레드에서 UI를 다루는 것을 말하는데 스위프트에서 GCD를 이용해 글로벌큐를 쓰는 것같은 것이다

모던한 컨커런시

현대적인 컨커런시 라는 말답게 대표적인것이 Swift 5버전에서 나온
async/await 그리고 actor이다
async/await를 통해 비동기 코드를 동기 코드처럼 작성할 수 있어 가독성이 좋아지고 비동기 코드들을 병렬로도 처리가 가능하며 동시성 작업을 더 안전하고 효율적으로 할수 있게 만들어준다.
또한 레이스 컨디션을 방지할만한 기능들이 생겼다

Async Await

비동기 작업을 동기처럼 작업을 할수있는데
async라는 블록 안에서 await를 하는 곳들을 비동기로 처리를 하고
그떄 오류가 나온다면 try/catch 로 에러 핸들링이 가능하며
여러 비동기처리를 클로저 보다 쉽게 처리하는 것이 가능하다

Structured Concurrency

async let req = requestGet()
// await를 확인하여 req가 값이 완성 될때까지 코드를 멈추고 다음 코드로 넘어감
let result = await req

async let 상수 방식을 사용해서
비동기 작업을 하는 것이다
상수가 완성이 가능할때까지 기다리도록 하여 완성이 되면 그 상수를 이용한 작업이 진행되도록 한다
await는 그 작업이 끝날 때까지 기다리게해서 값이 준비되지 않으면 그 작업이 완료될 때까지 다음 코드가 실행되지 않음
만약 await 를 안넣고 사용하면 당연히 오류가 발생함

Task, Task Group

Task {
    await withTaskGroup(of: String.self) { group in
        group.addTask {
            return await getData(id: 1)
        }

        group.addTask {
            return await getData(id: 2)
        }

        // 결과 처리
        for await result in group {
            print(result)  //getData(id:_) 한 1,2 데이터
        }
    }
}

Task

Task {
//비동기 처리 공간1
let result = await getData()
print(result) 
}

Task {
//비동기 처리 공간 2
let result2 = await getData2()
print(result) 
}

Task는 하나의 비동기 작업의 진행 단위라고 볼수있는데 await를 사용해 해당 비동기적으로 작업을 실행하고 결과를 반환한다

Task Group

func fetchGroupData() async {
    await withTaskGroup(of: String.self) { group in
        // 작업들을 group에 추가한다
        for i in 1...3 {
            group.addTask {
                await getData(id: i)
            }
        }

        // 작업 결과 처리
        for await result in group {
            print("data num /(result)")  
            // "data num 1", 
            "data num 2", 
            "data num 3"
        }
    }
}
Task {
    await fetchGroupData()
}

Task Group 은 여러 개의 비동기 작업을 병렬로 실행하며 모든 결과를 기다린 후에 처리가 가능하다
작업 결과를 병렬로 처리하고 작업이 완료되는 순서대로 결과를 반환한다

withTaskGroup으로 여러 작업을 병렬로 실행 후 for await를 사용해 작업들이 완료후 결과를 처리가 가능
Task Group은 작업이 완료되는 순서대로 결과를 반환한다

Task의 cancel

Task.cancel() 을 통하여 작업을 캔슬하는 것이 가능
타스크 그룹은 group.cancelAll() 을 통해 그룹내에서 캔슬이 가능

Actor

actor Counter { 
    private var num = 0

    func addNum() {
        num += 1
    }

    func getNum() -> Int {
        return num
    }
}

// 비동기 호출로 안전하게 상태 변경 및 읽기
let counter = Counter()

Task {
    await counter.addNum()
    print(await counter.getNum())  // 1
}

레이스 컨디션을 막고 동시성 안전성을 보장하는 객체이다
Class와 모양이 선언 되는 모양이 비슷한데 동시성 안전성을 보장하는 방식을 가지고있다
자체적으로 직렬 큐를 가지고 있어서 액터내로 접근하려고하면 모든 데이터는 직렬 큐에 들어가 순서대로 하나씩 처리가 되어서 레이스 컨디션이 발생하지 않는다
직렬큐라는 것이 있단건 몰랐을떄 여러곳에서 비동기로 접근하면 레이스 컨디션이 없을수 있나 했는데 어디서 접근하건 액터의 직렬큐로만 작업들이 들어가기에 레이스 컨디션은 발생할 수 없다

MainActor

@MainActor라는 어노테이션을 통해 해당 클래스가 메인 스레드에서 실행되도록 보장하기에 메인 스레드에서 해당 클래스가 동작하기에 동시성 문제가 발생하지 않는다

Actor Isolation

액터 격리를 통하여 액터 내부의 데이터는 다른 스레드에서 직접 접근할 수 없고 액터의 메서드를 통해서만 데이터를 읽거나 쓸 수 있다
private으로 하지 않더라도 액터 내부의 메소드로만 값이 변환된다

0개의 댓글