WWDC2021에 등장한 새로운 동시성 프로그래밍 모델
기존GCD의 코드 가독성, 에러 처리 등에 의한 문제점을 해소하기 위해 등장한 새로운 동시성 프로그래밍 모델
Thread가 과도하게 만들어져 비효율적이게 되는 현상
저렴한 context Switching 비용을 위해 생성되었으나, 그 수가 너무 많아 생기는 문제
결론: Queue는 Thread를 무조건 생성 하지 않음
GCD의sync는 Thread를 재사용
async는 놀고 있는 Thread가 없을 때, Thread를 생성 아니면 재사용
let q = DispatchQueue(label: "TestQueue")
q.sync {
sleep(1)
print("1", Thread.current)
}
DispatchQueue.main.async {
print("2", Thread.current)
}
q.async {
sleep(1)
print("3", Thread.current)
}
DispatchQueue.main.async {
print("4", Thread.current)
}
1 <_NSMainThread: 0x6000022900c0>{number = 1, name = main}
2 <_NSMainThread: 0x6000022900c0>{number = 1, name = main}
4 <_NSMainThread: 0x6000022900c0>{number = 1, name = main}
3 <_NSThread: 0x600002292840>{number = 5, name = (null)}
생성된 TestQueue는 sync일 경우에는 자신이 호출된 MainThread에서 동작하는 모습을 볼 수 있고,
async일 경우 Thread를 생성해서 그 위에서 동작하는 모습을 볼 수 있다.
async를 여러 군데에서 많이 사용할수록 과도하게 Thread가 생성되는 문제 발생
core수로 Thread가 많아지면 업무 처리 속도 저하 및 CPU 동작이 비효율적이 됨.await을 만나면 해당 비동기 함수의 작업은 일시 중단되고, Thread는 즉시 시스템에 반환async한 함수가 Thread에서 벗어나면 함수에 필요한 정보가 동작했던 Thread의 Stack에서 사라짐. (다른 작업에 필요한 정보를 Stack에 새로 담아야 하므로)heap 영역에 저장heap 영역에 저장Swift Concurrency: Async Frame
func processImageData1(completionBlock: (_ result: Image) -> Void) {
loadWebResource("dataprofile.txt") { dataResource in
loadWebResource("imagedata.dat") { imageResource in
decodeImage(dataResource, imageResource) { imageTmp in
dewarpAndCleanupImage(imageTmp) { imageResult in
completionBlock(imageResult)
}
}
}
}
}
processImageData1 { image in
display(image)
}
// (2a) Using a `guard` statement for each callback:
func processImageData2a(completionBlock: (_ result: Image?, _ error: Error?) -> Void) {
loadWebResource("dataprofile.txt") { dataResource, error in
guard let dataResource = dataResource else {
completionBlock(nil, error)
return
}
loadWebResource("imagedata.dat") { imageResource, error in
guard let imageResource = imageResource else {
completionBlock(nil, error)
return
}
decodeImage(dataResource, imageResource) { imageTmp, error in
guard let imageTmp = imageTmp else {
completionBlock(nil, error)
return
}
dewarpAndCleanupImage(imageTmp) { imageResult, error in
guard let imageResult = imageResult else {
completionBlock(nil, error)
return
}
completionBlock(imageResult)
}
}
}
}
}
processImageData2a { image, error in
guard let image = image else {
display("No image today", error)
return
}
display(image)
}
func processImageData3(recipient: Person, completionBlock: (_ result: Image) -> Void) {
let swizzle: (_ contents: Image) -> Void = {
// ... continuation closure that calls completionBlock eventually
}
if recipient.hasProfilePicture {
swizzle(recipient.profilePicture)
} else {
decodeImage { image in
swizzle(image)
}
}
}
swizzle이라는 메서드를 먼저 정의해야 아래의 조건에 따라 조건부 실행 가능asyncObject.asyncFunc { data, error in
self.dataCount += 1 // 해당 부분은 dataRace 문제 발생가능성 있음.
...
}
GCD에서 동기화를 처리해주기 위해선 DispatchQueue.sync, 뮤텍스, 세마포어와 같은 방법으로 처리해주어야한다.
하지만 개발자의 실수로 처리를 해주지 않아도 컴파일러가 오류 발생을 알려주지 않음.
try await asyncObject.asyncFunc { data in
self.dataCount += 1 // 컴파일 에러 발생
...
}
비 독립적(non-isolated) 구문이 변할 수 있는 프로퍼티에 접근하는 것을 금지한다는 메시지와 함께 컴파일 에러 발생
조금 더 개발자의 실수를 미연에 방지할 수 있음.
func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image
func processImageData() async throws -> Image {
let dataResource = try await loadWebResource("dataprofile.txt")
let imageResource = try await loadWebResource("imagedata.dat")
let imageTmp = try await decodeImage(dataResource, imageResource)
let imageResult = try await dewarpAndCleanupImage(imageTmp)
return imageResult
}
위에서 설명한 문제점이 어느정도 해결됨!
https://wwdcnotes.com/documentation/wwdcnotes/wwdc21-10254-swift-concurrency-behind-the-scenes/
https://swiftsenpai.com/swift/swift-concurrency-prevent-thread-explosion