관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 4. Concurrent Programming - Thread Safe Array 구현방법 (DispatchQueue의 barrier 사용) 본문

iOS 응용 (swift)

[iOS - swift] 4. Concurrent Programming - Thread Safe Array 구현방법 (DispatchQueue의 barrier 사용)

jake-kim 2022. 8. 14. 23:40

1. Concurrent Programming - NSLock, DispatchSemaphore 사용 방법

2. Concurrent Programming - DispatchSemaphore로 코틀린의 CompletableDeferred 구현방법

3. Concurrent Programming - DispatchQueue의 serial, concurrent, async, sync 이해하고 사용하기

4. Concurrent Programming - Thread Safe Array 구현방법 (DispatchQueue의 barrier 사용)

5. Concurrent Programming - OperationQueue로 동적으로 작업 추가, 취소하는 모듈 구현방법

 

* 해당 글을 이해하기 위해서는 이전 포스팅 글(DispatchQueue의 serial, concurrent, async, sync 이해하고 사용하기) 이해가 먼저 필수

기본 지식) DispatchQueue, sync와 async의 우선순위

  • sync, async는 이전 포스팅 글에서 알아본 대로, 해당 코드를 사용하는 스레드에 적용
  • 메인 스레드에서 async, sync하게 처리하는 작업이 여러개 존재할 때 우선순위는 async보다 sync가 높음
  • async는 결과가 끝나는 것을 기다리지 않고 다음 작업을 수행하여, 다른 작업에도 기능을 수행하여 현재 작업에 100%를 집중하지 않아 sync보다 결과를 늦게 반환하는 경우가 존재
// 짝수는 모두 sync로 한 결과, 홀수보다 짝수 print가 더 빨리 실행되는 결과

let serialQueue1 = DispatchQueue(label: "serial1")
let serialQueue2 = DispatchQueue(label: "serial2")
serialQueue1.async {
  print("job 1")
}
serialQueue2.sync {
  print("job 2")
}
serialQueue1.async {
  print("job 3")
}
serialQueue2.sync {
  print("job 4")
}
serialQueue1.async {
  print("job 5")
}
serialQueue2.sync {
  print("job 6")
}
serialQueue1.async {
  print("job 7")
}
serialQueue2.sync {
  print("job 8")
}

/*
job 2
job 1
job 4
job 6
job 8
job 3
job 5
job 7
*/
  • async보다 sync가 결과를 더 빠르게 내놓으므로 이 것을 활용하여 Thread safe 배열을 만들때, 읽기에는 곧바로 결과를 리턴해주는 sync를 사용하고, 쓰기에는 느리게 쓰여져도 되므로 async로 구현

Serial Queue에서의 Thread Safe Array 구현 아이디어

* Thread Safe: 어떤 함수나 변수 또는 객체가 멀티스레딩 환경에서 동시에 접근을 하더라도 프로그램 실행에 문제가 없음

* 상호 배제(mutual exclusion, Mutex): 동시성 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용되는 방법

  • Serial Queue에서의 Thread Safe 구현
    • serial 큐에서는 작업이 쌓여있어도, 큐에서 스레드로 보낸 작업이 끝나기 전까지는 큐에서 스레드로 다음 작업을 빼내지 않으므로 여러 스레드가 일을 처리하는 일이 없으므로, 특정 처리를 해주지 않아도 Thread safe함
    • 위에서 알아본 성능을 위해서 read에는 sync로, write에는 async로 접근
let serialQueue = DispatchQueue(label: "serial queue")
 
serialQueue.async() {
  // write
}
 
serialQueue.sync() {
  // read
}

Concurrent Queue에서의 Thread Safe Array 구현 아이디어

  • concurrent 큐에 작업이 쌓여 있으면 스레드로 작업을 한꺼번에 여러개를 보내는데, 이 때 write작업을 동시에 하게되면 memory conflict가 발생 (read할때는 값이 변경되지 않으므로 상관 x)
  • barrier라는 플래그를 사용하여 해결
  • barrier 플래그는 단순히 마치 serial하게 동작하도록 하는 것 (barrier 큐에 들어간 작업들은, 스레드로 작업을 보낼 때 하나씩만 보내며 이전 작업이 끝났을때만 다음 작업을 전송)
let concurrentQueue = DispatchQueue(label: "concurrent queue")
 
concurrentQueue.async(flags: .barrier) {
  // write
}
 
concurrentQueue.sync() {
  // read
}

Thread Safe Array 구현

  • 동시성 작업이 필요한 경우, serial queue보단 concurrent queue가 성능이 더 좋을 것이므로 concurrent queue를 사용
  • 성능을 위해 read할땐 sync로, write할땐 async로 접근
  • concurrent queue를 사용하면서의 예상되는 문제점은 write할때 async로 접근할때 스레드 세이프하지 않을수 있지만, barrier를 사용하면 해결가능
final class ThreadSafeArray<T> {
  private var array = [T]()
  private let queue = DispatchQueue(label: "Thread Safe Array", attributes: .concurrent)
}

// Operator
extension ThreadSafeArray {
  func first() -> T? {
    queue.sync {
      self.array.first
    }
  }
  
  func removeLast() {
    queue.async(flags: .barrier) {
      if !self.array.isEmpty {
        self.array.remove(at: self.array.count - 1)
      }
    }
  }
}

* 전체 코드: https://github.com/JK0369/ExThreadSafeArray

* 참고

https://ko.wikipedia.org/wiki/%EC%83%81%ED%98%B8_%EB%B0%B0%EC%A0%9C

https://zamzam.io/creating-thread-safe-arrays-in-swift/

 

Comments