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이 서로 다른 스레드에서 일어날 때 사용
참조