프로그래밍언어

[Swift] Actor와 Structured Concurrency

애기공룡훈련병 2025. 2. 16. 07:44
반응형

Swift는 동시성 프로그래밍을 보다 안전하고 직관적으로 수행할 수 있도록 Swift Concurrency를 도입했습니다. 그중에서도 actorstructured concurrency는 동시성을 효과적으로 관리하고 데이터 경쟁 문제를 해결하는 핵심 개념입니다.

이 블로그에서는 Swift의 actor 개념과 동시성 문제 해결 방법, 그리고 structured concurrency의 주요 구성 요소인 async letTaskGroup을 사용한 프로그래밍 방법을 상세히 설명하고, 이를 활용한 효과적인 비동기 프로그래밍 패턴을 소개하겠습니다.

1. Actor

Actor의 개념

Actor는 Swift에서 동시성 환경에서 공유 데이터에 안전하게 접근할 수 있도록 제공되는 객체 유형입니다.

Actor는 데이터 경쟁(data race) 문제를 방지하며, 내부 상태를 보호하면서 비동기적으로 작업을 수행할 수 있도록 합니다.

actor Counter {
    private var value: Int = 0
    
    func increment() {
        value += 1
    }
    
    func getValue() -> Int {
        return value
    }
}

위 코드에서 Counter actor는 내부 value 변수를 관리하며, incrementgetValue 메서드를 통해 값을 조작할 수 있습니다.

Actor의 동시성 문제 해결 방법

  • Race Condition 방지: Actor는 내부 상태를 외부에서 직접 접근할 수 없도록 보호하여, 멀티스레드 환경에서 동시에 수정되더라도 안전하게 동작합니다.
  • 직렬화된 액세스: Actor 내부의 메서드는 하나의 작업만 동시에 실행되도록 보장됩니다. 따라서 여러 개의 태스크가 동일한 actor를 호출해도, 해당 작업들이 직렬화되어 실행됩니다.
  • 비동기 메서드 지원: Actor의 메서드는 async 키워드가 붙어야 외부에서 호출할 수 있으며, Swift의 await을 사용해 동기적인 코드처럼 사용할 수 있습니다.
let counter = Counter()

Task {
    await counter.increment()
    let currentValue = await counter.getValue()
    print("Counter value: \(currentValue)")
}

2. Structured Concurrency (구조적 동시성)

Structured Concurrency는 Swift에서 비동기 작업을 체계적으로 구성하는 방법을 의미합니다. 대표적인 기법으로 async letTaskGroup이 있으며, 이들을 활용하면 코드를 보다 간결하고 안전하게 작성할 수 있습니다.

async let을 사용한 동시 실행

async let을 사용하면 비동기 작업을 병렬 실행할 수 있습니다. 이는 특히 여러 개의 독립적인 작업을 동시에 실행해야 할 때 유용합니다.

예제: 여러 개의 네트워크 요청을 동시에 실행하기

func fetchUserProfile() async -> String {
    // 네트워크 요청을 시뮬레이션
    try? await Task.sleep(nanoseconds: 2_000_000_000)
    return "User Profile Data"
}

func fetchUserPosts() async -> String {
    try? await Task.sleep(nanoseconds: 2_000_000_000)
    return "User Posts Data"
}

func fetchUserData() async {
    async let profile = fetchUserProfile()
    async let posts = fetchUserPosts()
    
    let result = await (profile, posts)
    print("Profile: \(result.0), Posts: \(result.1)")
}

Task {
    await fetchUserData()
}

위 코드에서 fetchUserProfile()fetchUserPosts() 함수는 각각 2초의 딜레이가 있는 비동기 함수입니다. async let을 사용하면 두 함수가 동시에 실행되며, await을 통해 최종 결과를 기다립니다.

TaskGroup을 사용한 동적 태스크 관리

TaskGroup을 사용하면 여러 개의 비동기 태스크를 동적으로 추가 및 실행할 수 있습니다.

예제: 여러 개의 데이터 요청을 동적으로 수행하기

import Foundation

func fetchData(from urls: [URL]) async -> [String] {
    await withTaskGroup(of: String.self) { group in
        for url in urls {
            group.addTask {
                return await fetch(from: url)
            }
        }
        
        var results: [String] = []
        for await result in group {
            results.append(result)
        }
        return results
    }
}

func fetch(from url: URL) async -> String {
    try? await Task.sleep(nanoseconds: 1_000_000_000) // 1초 딜레이
    return "Data from \(url.absoluteString)"
}

Task {
    let urls = [
        URL(string: "https://example.com/1")!,
        URL(string: "https://example.com/2")!,
        URL(string: "https://example.com/3")!
    ]
    let data = await fetchData(from: urls)
    print(data)
}
  • withTaskGroup(of: String.self)을 사용해 여러 개의 데이터를 병렬로 가져옵니다.
  • group.addTask {}로 새로운 태스크를 추가하여 동적으로 처리합니다.
  • for await result in group {}를 사용해 모든 태스크의 완료를 기다립니다.

3. Actor와 Structured Concurrency를 활용한 효과적인 비동기 프로그래밍 패턴

Actor + async let을 활용한 안전한 동시성 프로그래밍

actor BankAccount {
    private var balance: Int = 0
    
    func deposit(amount: Int) {
        balance += amount
    }
    
    func getBalance() -> Int {
        return balance
    }
}

let account = BankAccount()

Task {
    async let deposit1 = account.deposit(amount: 100)
    async let deposit2 = account.deposit(amount: 200)
    
    await deposit1
    await deposit2
    
    print("Final balance: \(await account.getBalance())")
}

Actor + TaskGroup을 활용한 대량 데이터 처리

actor DataProcessor {
    private var processedData: [String] = []
    
    func addData(_ data: String) {
        processedData.append(data)
    }
    
    func getData() -> [String] {
        return processedData
    }
}

 

반응형