[swift] 스레드 간의 데이터 공유와 동기화

앱 개발에서 여러 스레드가 동시에 실행되는 경우에는 스레드 간의 데이터 공유와 동기화를 고려해야 합니다. 스레드 간의 데이터 공유를 올바르게 처리하지 않으면 예기치 않은 결과가 발생할 수 있고, 동기화가 필요한 경우에는 데이터의 일관성을 보장하기 위해 동기화 기법을 사용해야 합니다.

데이터 공유

여러 스레드가 공유하는 데이터는 동시에 접근할 수 있기 때문에, 데이터의 일관성을 유지하기 위해서는 적절한 방법으로 데이터를 공유해야 합니다. Swift에서는 여러 스레드에서 안전하게 데이터를 공유하기 위해 다음과 같은 방법을 제공합니다.

1. 자원을 읽기 전용으로 공유

여러 스레드에서 참조만 하는 경우에는 데이터를 읽기 전용으로 공유하는 것이 안전합니다. 읽기 전용으로 공유되는 데이터는 동기화가 필요하지 않습니다. 이 경우 데이터를 변경하지 않는 것이 중요합니다.

2. Critical Section 및 동시성 큐 사용

Critical Section은 스레드가 동시에 접근하지 못하도록 보호되는 코드 영역을 의미합니다. Critical Section에는 데이터의 일관성을 유지하기 위해 데이터 접근을 제어하는 코드를 포함합니다. Critical Section에 접근하기 위해서는 동시성 큐를 사용하여 스레드 간의 순서를 조절해야 합니다.

Swift에서는 DispatchQueue를 사용하여 동시성 큐를 생성하고, 스레드 간의 데이터 접근을 조절할 수 있습니다. 예를 들어, 아래 코드는 Critical Section에 대한 동시성 큐를 생성하고, 데이터 추가 및 제거를 보호하는 예시입니다.

class ThreadSafeArray<Element> {
    private var array = [Element]()
    private let accessQueue = DispatchQueue(label: "com.example.ThreadSafeArray", attributes: .concurrent)

    func append(element: Element) {
        accessQueue.async(flags: .barrier) {
            self.array.append(element)
        }
    }

    func remove(at index: Int) {
        accessQueue.async(flags: .barrier) {
            self.array.remove(at: index)
        }
    }

    func count() -> Int {
        var count = 0
        accessQueue.sync {
            count = self.array.count
        }
        return count
    }
}

위의 코드에서 accessQueue는 동시성 큐로, appendremove 메서드는 .barrier 플래그를 사용하여 Critical Section으로 보호됩니다. count 메서드는 sync 메서드를 사용하여 큐에 동기적으로 접근하므로 데이터 일관성을 유지합니다.

동기화

스레드 간에 데이터를 공유해야 하는 경우, 동기화를 통해 데이터의 일관성을 보장할 수 있습니다. Swift는 여러 동기화 기법을 제공하는데, 가장 일반적인 동기화 기법은 Lock 또는 Semaphore을 사용하는 것입니다.

1. Lock

Lock은 특정 코드 영역에 대한 동시 접근을 제한하는 동기화 기법입니다. Swift에서는 NSLock을 사용하여 Lock을 생성하고, lockunlock 메서드를 사용하여 잠그고 해제할 수 있습니다. 아래는 NSLock를 사용한 예시 코드입니다.

import Foundation

let lock = NSLock()

func someFunction() {
    lock.lock()
    defer {
        lock.unlock()
    }
    // 공유 데이터에 대한 접근 코드
}

lock 메서드를 호출하여 다른 스레드가 접근하지 못하도록 잠근 후, defer문을 사용하여 블록을 벗어나기 전에 unlock 메서드를 호출하여 잠금을 해제합니다.

2. Semaphore

Semaphore는 특정 개수의 스레드만 동시에 접근할 수 있도록 제한하는 동기화 기법입니다. Swift에서는 DispatchSemaphore을 사용하여 Semaphore를 생성하고, waitsignal 메서드를 사용하여 제어할 수 있습니다. 아래는 DispatchSemaphore을 사용한 예시 코드입니다.

import Dispatch

let semaphore = DispatchSemaphore(value: 1)

func someFunction() {
    semaphore.wait()
    defer {
        semaphore.signal()
    }
    // 공유 데이터에 대한 접근 코드
}

wait 메서드를 호출하여 Semaphore 내의 값을 감소시킴으로써 스레드가 접근하게 하거나, 값을 기다리는 상태로 만듭니다. signal 메서드는 Semaphore 내의 값을 증가시켜 다른 스레드가 접근할 수 있도록 합니다.

참고 자료