swift5.5 스레드 동시성 문제 및 해결 방안 (async/await, Actor)

동시성 문제는 여러 스레드 또는 동시에 실행되는 코드가 공유된 데이터에 액세스할 때 발생할 수 있습니다. 동시성 문제의 대표적인 예시는 "계좌 이체" 문제입니다.

계좌 이체를 예로 들어 볼 수 있습니다. 두 개의 계좌 A와 B가 있고, A 계좌에서 B 계좌로 일정 금액을 이체하는 상황을 가정해 보겠습니다.

class BankAccount {
    private(set) var balance: Int

    init(balance: Int) {
        self.balance = balance
    }

    func deposit(_ amount: Int) {
        balance += amount
    }

    func withdraw(_ amount: Int) {
        balance -= amount
    }
}

func transfer(from accountA: BankAccount, to accountB: BankAccount, amount: Int) {
    accountA.withdraw(amount)
    accountB.deposit(amount)
}

스레드 A와 스레드 B가 동시에 transfer 함수를 호출하여 서로 다른 계좌에서 동시에 이체를 진행한다고 가정합니다. 이 경우, 동시성 문제가 발생할 수 있습니다.

예를 들어, 스레드 A가 계좌 A에서 금액을 인출한 후, 아직 스레드 B가 계좌 B에 입금하기 전에 스레드 B가 실행되어 계좌 B에서 인출을 진행할 수 있습니다. 이렇게 되면 두 스레드가 동시에 계좌 B에 입금하기 전에 금액을 인출하게 되어 계좌의 잔액이 음수가 될 수 있습니다. 이처럼 동시성 문제는 올바르지 않은 상태가 발생할 수 있습니다.

이 문제를 해결하기 위해 동기화 메커니즘을 사용해야 합니다. Swift의 actor 또는 DispatchSemaphore와 같은 동기화 도구를 사용하여 두 스레드가 동시에 공유된 데이터에 액세스하지 못하게 하여 동시성 문제를 방지할 수 있습니다.

actor BankAccount {
    private(set) var balance: Int

    init(balance: Int) {
        self.balance = balance
    }

    func deposit(_ amount: Int) {
        balance += amount
    }

    func withdraw(_ amount: Int) {
        balance -= amount
    }
}

// 이제 transfer 함수를 비동기로 변경하고 actor를 사용합니다.
func transfer(from accountA: BankAccount, to accountB: BankAccount, amount: Int) async {
    await accountA.withdraw(amount)
    await accountB.deposit(amount)
}

// 사용 예
async {
    let accountA = BankAccount(balance: 1000)
    let accountB = BankAccount(balance: 500)

    // 이제 비동기로 이체 작업을 수행하고, 완료되면 결과를 출력합니다.
    await transfer(from: accountA, to: accountB, amount: 200)
    print("Account A balance: \(await accountA.balance)")
    print("Account B balance: \(await accountB.balance)")
}

위 코드에서 BankAccount 클래스를 actor로 변경했습니다. 
이렇게 하면 BankAccount의 내부 상태에 대한 동시 접근이 자동으로 동기화되어 데이터 레이스 조건을 방지합니다.

transfer 함수를 비동기로 변경하고, async/await를 사용하여 계좌 이체 작업을 수행합니다. 
이제 transfer 함수에서 await 키워드를 사용하여 actor의 메서드를 호출하면, 
메서드 호출이 순차적으로 실행되어 동시성 문제가 발생하지 않습니다.

이렇게 actor와 async/await를 사용하여 동시성 문제를 해결할 수 있습니다. 
 
감사합니다.