GCD에 대해 (Thread-Safe)

JinSeok Hong·2021년 10월 25일
0

앞서 멀티스레드 환경에서 동시성을 사용한다면 '공유자원' 적인 측면에서 문제가 생겨 경쟁상황이 발생할 수 있다고 언급했다. 여러 스레드가 동시에 쓰여도 한번에 한개의 스레드만 접근하도록 처리하여 안전하도록, 세마포어를 사용해서 문제를 해결했다 (DispatchSemaphore(value : 1) 와 같은 방법으로 Thread-Safe한 환경을 만들어주기)

이밖에 몇가지 해결책들을 더 알아볼 생각이다.

Lock


흔히 mutex lock이라고 말한다. 쉽게 얘기하면 이진 세마포어와 유사하게 동작한다. wait과 signal을 통해 자원에 접근하는 Signaling 기법을 사용하는 Semaphore와 달리 Lock이라는 것은 critical section에 들어가기 전에 lock을 획득하고, 나올 때는 직접 해제하는 방법이다. 세마포어에서는 Lock을 걸지 않은 스레드도 signal을 사용해 Lock을 해제할 수 있지만 Mutex에서는 Lock을 걸었던 스레드만 Critical section에서 나갈 때 직접 Lock을 해제할 수 있기 때문에 이진에 적합하고 임의로 접근 프로세스 수를 정할 수 있는 세마포어와는 다르다. 다만 하드웨어 명령어를 이용하는 세마포어 비해 직접 소프트웨어 적으로 atomic하게 실행을 처리해야하는 등 구현에 어려움이 많아 직접 커스텀해서 쓰기는 어렵다고 한다.

Serial Queue, Sync


동기 메소드는 경쟁 상황을 피하는데 매우 유용하다. async 비동기 작업 중 어떤 한 객체의 값에 대해서 읽는 작업은 어디서나 일어나도 상관없지만 읽기/쓰기가 동기에 일어나거나, 수정하는 작업이 여기저기서 일어난다면 Thread-Safe하지 못한 문제가 생길 것이다. 우리는 이러한 변화의 순서를 보장해야하고 객체에 대한 처리를 분명하게 해줄 필요가 있다. 이것을 위해 객체에 접근하는 유일한 방법으로 하나의 Serial 큐를 제공하여 동시에 접근하는 것을 막고, 작업의 순서를 보장하게 하는 방법을 쓸 수 있다. 메인큐에서는 sync를 사용해서는 안되기에 DispatchQueue.global().async{} 내 등 비동기 작업을 처리할 때 구현해주면 된다. 즉 유일한 객체에 접근할 때 async 비동기 작업 중 sync 메소드 이용한다. 라는 의미이다.

private serialQueue = DispatchQueue(label:"...")
private var _count = 0 
public var count : Int {
    get {
        return serialQueue.sync{
            _count
        }
    }
    set {
        serialQueue.sync {
            _count = newValue
        }
    }
}

DispatchQueue.global().async{
    //count에 대한 작업
    //global() 큐에서 Serial 큐로 보낸 작업을 기다릴 것이다.
}

Dispatch Barrier

앞서 언급한 동기 직렬 큐보다는 조금 더 효율적인 방법이다. Concurrent 큐 내의 여러개 스레드 중에서 특정 작업을 한개의 스레드만 사용해 직렬로 실행가능하게 하여 순서를 보장하는 것이다. 일반적으로 쓰기와 같은 객체의 값을 수정하는 시점을 barrier 설정을 해주면 여러 스레드가 접근하는 동시에 접근하는 것을 막아줄 것이다. 이것은 Sync, Serial Queue 와 달리 동시성을 보장하기 때문에 아무래도 멀티스레드이자 Thread-safe함으로 인해 효율이 좋은 것은 당연하다. 사용 방법은 아래와 같다.

private let concurrentQueue = DispatchQueue(label: "...", attributes: .concurrent)
private let _count = 0
public var count : Int {
    get{
        return concurrentQueue.sync {
            _count
        }
    }
    set {
        concurrentQueue.async(flags: .barrier) { [unowned self] in
            self._count = newValue
        }
    }
}


0개의 댓글