Swift

Swift) Concurrency, GCD

kangwook 2022. 9. 20. 22:09

Thread Safety를 구현하는 방법

  • Thread Safety란 동시에 여러 스레드에서 인스턴스에 접근할 때 프로그램의 실행에 문제가 없다는 것을 뜻함

GCD

  • 현재 iOS개발에서 주로 사용하는 동시성 프로그래밍 API는 GCD(Grand Central Dispatch)
  • GCD를 사용하면 async로 작업을 수행하고 보통 탈출 클로저를 이용한 completion handler를 통해 작업이 끝났을 경우에 대해 처리

Swift Concurrency

  • WWDC2021에서 새로 소개된 동시성 프로그래밍 API
  • async / await 키워드를 이용해 비동기 태스크 종료 후 코드를 작성할 수 있음
  • actor를 이용해서 작업에 대한 동시성 코드를 안전하게 공유할 수 있음(iOS 14 이상)

GCD vs. Swift Concurrency

GCD

func processImageData1(completionBlock: (_ result: Image) -> Void) {
    loadWebResource("dataprofile.txt") { dataResource in
        loadWebResource("imagedata.dat") { imageResource in
            decodeImage(dataResource, imageResource) { imageTemp in
                dewarpAndClenaupImage(imageTemp) { imageResult in
                    comletionBlock(imageResult)
                }
            }
        }
    }  
}

Swift Concurrency

func processImageData() async throws -> Image {
    let dataResource = try await loadWebResource("dataprofile.txt")
    let imageResource = try await loadWebResource("imagedata.dat")
    let imageTemp = try await decodeImage(dataResource, imageResource)
    let imageResult = try await dewarpAndCleanupImage(imageTemp)
    return imageResult
}

 

에러 핸들링 안정성

  • 기존 GCD 방식에서는 completionHandler에 데이터와 에러 정보를 같이 보내주는 방법을 주로 사용

GCD

func downloadImageWithURL(url: URL, session: URLSession, completionHandler: @escaping (UIImage?, Error?) -> Void) {
    session.dataTask(with: url) { data, response, error in
        guard let imageData = data else {
            completionHandler(nil, DownloadManagerError.invalidData)
            return     
        }
 
        guard let urlResponse = response as? HTTPURLResponse,
              urlResponse.statusCode == 200 else {
            completionHandler(nil, DownloadManagerError.networkFail)
            return
        }
 
        let image = UIImage(data: imageData)
        completionHandler(image, nil)
    }.resume()
}

Swift Concurrency

  • Swift Concurrency를 작성하면 데이터는 return으로, error는 throw로 분리해서 전달할 수 있음
@available(iOS 15.0, *)
func downloadImageWithURLSwiftConcurrency(url: URL, session: URLSession) async throws -> UIImage {
    let (data, response) = try await session.data(from: url)
    guard let urlResponse = response as? HTTPURLResponse,
          urlResponse.statusCode == 200 else {
        throw DownloadManagerError.networkFail
    }
 
    guard let image = UIImage(data: data) else {
        throw DownloadManagerError.invalidData
    }
 
    return  image
}

 

동기화 처리

  • GCD에서 동기화를 안전하게 처리하는 방법은 DispatchQueue.sync를 통해 순서대로 접근하는 것을 보장하거나 mutex나 semaphore를 이용하는 방법이 있음
  • 하지만 컴파일러가 동기화를 올바르게 처리했는지 확인해주지는 않기 때문에 버그가 발생할 수 있음
    • 이러한 과정에서 발생한 버그는 개발자가 유의해서 코드를 작성하거나 테스팅 및 디버깅을 통해 확인해야 함
  • 다음과 같은 코드가 있을 때, 데이터 레이스가 발생할 수 있음
    • 데이터 레이스란 멀티 스레드가 같은 데이터를 이용하고 스레드에서 그것을 업데이트할 때 발생

GCD

for _ in 0..<numberOfImages {
    downloadManager.downloadImageWithURL(url: url, session: session) { image, error in
        self.finishedImages += 1    // 에러가 발생하는데 테스팅이나 디버깅을 통해 찾아내야 함
        guard let downloadedImage = image else  { return }
        let progress = Float(self.finishedImages) / Float(self.numberOfImages)
        self.updateUs(image: downloadedImage, progress: progress)
    }
}

Swift Concurrency

try await withThrowingTaskGroup(of: UIImage.self) { group in
    for _ in 0..<self.numberOfImages {
        group.async(priority: .background) {
            let image = try await self.downloadManager.downloadImageWithURLSwiftConcurrency(url: url, session: session)
            self.finishedImages += 1 // Cause compiler error;
            await self.updateUIs(image: image)
            return image
}

 

Mutex vs. Semaphore

Mutex

  • 공유된 자원의 데이터 혹은 임계영역 등에 하나의 Process 혹은 Thread가 접근하는 것을 막아줌(동기화 대상이 하나)
  • 자원을 소유할 수 있고 책임을 가짐
  • Lock을 소유하고 있는 스레드만이 이 Mutex를 해제할 수 있음

Semaphore

  • 공유된 자원의 데이터 혹은 임계영역 등에 여러 Process 혹은 Thread가 접근하는 것을 막아줌(동기화 대상이 하나 이상)
  • Semaphore를 소유하지 않는 스레드가 Semaphore를 해제할 수 있음

NSLock

  • Mutex의 한 종류
  • NSLock은 timeout기능을 제공하는데, 데드락이 발생할 경우 아무일도 일어나지 않는 경우에 대해 해결할 수 있음

DispatchSemaphore

  • Semaphore의 한 종류
  • lock과 unlock이 서로 다른 스레드에서 일어날 때 사용

 

참조