# Concurrency 비동기 코드와 병렬코드를 구조적으로 지원하는 방법이 스위프트에 내장되어 있다. 비동기 코드는 네트워킹 작업같은 긴 작업 중에도 짧은 UI 업데이트를 할 수 있도록 한다.병렬 코드는 여러개의 코드를 동시에 실행하는 것을 의미한다. 예를들어 4코어 컴퓨터는 각 코어당 하나씩 수행을 맡아서 동시에 4개의 코드를 실행할 수 있다. 병렬코드와 비동기 코드를 사용하는 프로그램은 memory-safe한 코드를 쉽게 작성할 수 있도록 한다. 동시성을 추가하는 것은 디버그하기 어렵게 만들 수도 있다. 그러나 스위프트 에서는 컴파일 타임에 잡아낼 수 있다. >스위프트에서 스레드와 직접 상호작용 하지 않는다. 자신이 실행되던 쓰레드에 다른 비동기 함수가 먼저 들어와서 실행될 수 있도록 한다. 비동기 함수가 실행되면, 어느 쓰레드에서 실행되는지 보장할 수 없다. 스위프트가 지원하는 동시성 언어를 사용하지 않으면 읽기 힘든 코드가 된다. completion handler의 연속이 될 것이다. + async를 사용하면 throw를 해줄 수 있으니 컴파일 타임에 오류를 잡아낼 수 있는 것이다. - 언쳉 ## Defining and Calling Asynchronous Functions 비동기 메서드는 실행중 중지할 수 있다. 동기 함수와는 대조적인 것이다. 비동기 메서드 임을 명시해주는 방법이다. ```swift func listPhotos(inGallery name: String) async -> [String] { let result = // ... some asynchronous networking code ... return result } ``` throw가 있으면 async throw 순으로 작성한다. 에러가 날 수 있는 곳에 try 를 작성해주는 것처럼 중지가 될 수 있는 곳에 `await` 키워드를 붙여준다. ```swift let photoNames = await listPhotos(inGallery: "Summer Vacation") let sortedNames = photoNames.sorted() let name = sortedNames[0] let photo = await downloadPhoto(named: name) show(photo) ``` 실행순서이다. 1. `listPhotos()` 메서드가 실행되고, 반환될 때까지 기다린다. 2. 위 코드가 중지된 동안 다른 동시성 코드가 실행된다. 2-1. 4. `listPhotos()` 가 반환되고, 이 시점에서 다시 시작한다. 5. sortedNames와 name 은 동기코드이다. 평범하게 실행된다. 6. `downloadPhoto()` 메서드가 실행되고 반환될때까지 기다린다. 마찬가지로 다른 동시성 코드를 실행할 기회를 준다. 7. 위 코드가 반환되고, photo로 할당된 뒤, show를 한다. 쓰레드 양보 라고도 불린다. 왜냐하면 스위프트는 현재 스레드를 중지하고 대신 다른 코드를 현재스레드에 실행시키는 것이다. await이 쓰여진 코드는 실행을 중지할 수 있어야 해서, 어떤 특정 장소에서만 비동기 함수를 호출할 수 있다 : - Code in the body of an asynchronous function, method, or property. - Code in the static main() method of a structure, class, or enumeration that’s marked with @main. - Code in an unstructured child task, as shown in Unstructured Concurrency below. 중단가능한 시점 사이의 코드는 다른 동시코드로부터 방해될 가능성 없이. 연속적으로 실행된다. ```swift let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0] add(firstPhoto, toGallery: "Road Trip") // At this point, firstPhoto is temporarily in both galleries. remove(firstPhoto, fromGallery: "Summer Vacation") ``` add() 와 remove() 사이에 다른 코드가 실행될 수 없다. firstPhoto 가 임시적으로 두 갤러리에 동시에 존재하게 된다. 이는 앱의 불변성을 위반한다. 이 메서드 사이에 await 코드가 오면 안된다는 것을 명시하기 위해 이렇게 작성할 수 있다. ```swift func move(_ photoName: String, from source: String, to destination: String) { add(photoName, to: destination) remove(photoName, from: source) } // ... let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0] move(firstPhoto, from: "Summer Vacation", to: "Road Trip") ``` move() 메서드는 동기적이기 때문에, 중지가능 시점을 포함할 수 없다는 보장을 할 수 있다. ## Asynchronous Sequences ```swift import Foundation let handle = FileHandle.standardInput for try await line in handle.bytes.lines { print(line) } ``` AsyncSequence를 채택해주면 이렇게 사용할 수 있다. ## Calling Asynchronous Functions in Parallel await을 통해 비동기메서드를 호출하면 한번에 한 작업밖에 수행할 수 없다. ```swift let firstPhoto = await downloadPhoto(named: photoNames[0]) let secondPhoto = await downloadPhoto(named: photoNames[1]) let thirdPhoto = await downloadPhoto(named: photoNames[2]) let photos = [firstPhoto, secondPhoto, thirdPhoto] show(photos) ``` 이렇게 하면 비동기 메서드임에도 불구하고 불필요하게 기다려 주어야 한다. 동시에 실행시켜줄 수 없나? ```swift async let firstPhoto = downloadPhoto(named: photoNames[0]) async let secondPhoto = downloadPhoto(named: photoNames[1]) async let thirdPhoto = downloadPhoto(named: photoNames[2]) let photos = await [firstPhoto, secondPhoto, thirdPhoto] show(photos) ``` - 다음 코드줄이 비동기 메서드의 결과에 의존한다면 await을 사용해라. (예를 들면 결과를 할당받아 다음 줄에서 프린트를 한다거나) - 이후에 비동기 메서드의 결과가 필요한 것이 아니면 async-let을 이용해라. 그럼 병렬 수행을 할 수 있다. - await과 async-let 둘다 중지되었을 때 다른 코드를 수행할 수 있다. #### 내가 짜본 예시 ```swift func listPhotos() async throws -> [String] { print("네트워킹 작업중 ...") try await Task.sleep(nanoseconds: 3000000000) return ["1", "2", "3"] } print("start") async let result = listPhotos() async let result2 = listPhotos() async let result3 = listPhotos() let strs = try await [result, result2, result3] print(strs) ``` 병렬 작업을 하는 방법은 위 코드와 같이 async let 으로 선언하고 마지막에 기다려주면 3개의 작업이 동시에 실행된다. ## Tasks and Task Groups task는 비동기로 실행될 수 있는 단위이다. task 는 자식을 가질 수 있다. 이는 구조적 동시성이라고 부른다. 정확성에 대한 일부 책임을 져야 하지만, 작업 간의 명시적인 부모-자식 관계를 통해 Swift는 취소 전파와 같은 몇 가지 동작을 처리할 수 있으며, Swift는 컴파일 타임에 일부 오류를 감지할 수 있습니다. ```swift await withTaskGroup(of: Data.self) { taskGroup in let photoNames = await listPhotos(inGallery: "Summer Vacation") for name in photoNames { taskGroup.addTask { await downloadPhoto(named: name)} } } ``` ### Unstructured Concurrency ### Task Cancellation ## Actors ## Sendable Types 한 동시성 도메인에서 다른 곳으로 공유될 수 있는 타입을 Sendable 타입이라 한다. 가변상태를 포함하는 몇몇 타입은 동시성 도메인 사이에 공유될 때 안전하지 않다. Sendable이 되기위한 3가지 방법: - 값타입 이어야 하고, 가변상태가 다른 sendable 데이터로 만들어진 것이어야 한다. 예를들어 sendable한 저장프로퍼티를 가지는 구조체, 또는 sendable한 associated value를 가진 enum - 가변상태를 일체 가지면 안되며, 불변상태는 sendable 데이터로 만들어진 것이어야 한다. 예를들어 읽기전용 프로퍼티만 가지는 클래스 또는 구조체 - 가변상태에 안정성을 보장해야 한다. 예를들어 @MainActor 마크된 클래스, 또는 특정 스레드 또는 큐의 프로퍼티에 대한 액세스를 직렬화하는 클래스. ## Reference - [Swift Programming Language - Concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html)