Swift - 18. 동시성 Concurrency
Swift는 구조화된 방식으로 비동기(asynchronous)와 병렬(parallel) 코드 작성을 지원한다.
- 비동기 코드는 일시적으로 중단되었다가 다시 실행할 수 있지만, 한 번에 프로그램의 한 부분만 실행된다. 짧은 작업을 계속 진행하면서 긴 실행 작업을 계속한다.
- 병렬 코드는 동시에 코드의 여러 부분이 실행되는 것이다.
병렬 또는 비동기 코드는 추가 스케줄링의 유연성도 있지만 한편 복잡성이 증가하기도 한다.
비동기 함수/메소드 정의
- 실행 도중에 일시적으로 중단될 수 있는 특수한 함수/메소드
- 바디 내에서 실행을 일시중지할 수 있는 부분을 표시함
- 파라미터 뒤, 반환 화살표 전에
async
키워드 - throwing function이자 비동기 함수/메소드는
throws
전에async
를 작성
func listPhotos(inGallery name: String) async -> [String] {
let result = // ... some asynchronous networking code ...
return result
}
호출
- 비동기 메소드를 호출할 때 해당 메소드가 반환될 때까지 실행이 일시 중단됨
- 중단될 가능성이 있는 지점을, 호출 앞에
await
키워드로 표시 - 비동기 메소드 내에서 실행 흐름은 다른 비동기 메소드를 호출할 때만 일시 중단됨
let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)
갤러리에 모든 사진의 이름을 가져온 다음 첫번째 사진을 보여주는 코드
listPhotos(inGallery:)
와 downloadPhoto(named:)
함수 모두 네트워크 요청을 필요로 하기 때문에 완료하는데 비교적 오랜시간이 걸릴 수 있다.
반환 화살표 전에 async
를 작성하여 둘 다 비동기로 만들면 이 코드는 그림이 준비될 때까지 기다리는 동안 앱의 나머지 코드가 계속 실행될 수 있다.
위 예제의 동시성을 이해하기 위한 실행 순서
1. 실행을 시작 ~ 첫번째 await
까지 실행된다. listPhotos(inGallery:)
함수를 호출하고 반환될 때까지 실행을 일시 중단한다.
2. 실행이 일시 중단되는 동안 같은 프로그램의 다른 코드가 동시에 실행된다. (예를 들어 오랜 시간 실행되어야 하는 백그라운드 작업인 새 사진의 목록 업데이트 등을 할 수 있다.) 이 코드는 await
로 표시된 다음 중단 지점 또는 완료될 때까지 실행된다.
3. listPhotos(inGallery:)
가 반환된 후에 이 코드는 해당 지점에서 시작하여 계속 실행된다. 반환된 값을 photoNames
에 할당한다.
4. sortedNames
와 name
을 정의하는 라인은 일반적인 동기 코드이다.
5. 다음 await
는 downloadPhoto(named:)
함수에 대한 호출을 표시한다.
6. downloadPhoto(named:)
가 반환된 후에 반환값은 photo
에 할당된 다음에 show(_:)
를 호출할 때 인수로 전달된다.
Swift가 현재 스레드에서 코드의 실행을 일시 중단하고 대신 해당 스레드에서 다른 코드를 실행하기 때문에 스레드 양보(yielding the thread)라고도 부른다.
await
가 있는 코드는 실행을 일시 중단할 수 있어야 하므로 프로그램의 특정 위치에서만 비동기 함수/메소드를 호출할 수 있다.
- 비동기 함수, 메소드 또는 프로퍼티의 바디에 있는 코드
@main
으로 표시된 구조체, 클래스, 또는 열거형의 정적 (static)main()
메소드에 있는 코드- 구조화되지 않은 하위 작업의 코드
Task.sleep(_:)
메소드는 동시성 작동 방식을 위해 간단한 코드를 작성할 때 유용하다. 이 메소드는 아무런 동작도 하지 않지만 반환되기 전에 주어진 나노 단위의 초만큼 기다린다. 다음은 네트워크 작업 대기를 시뮬레이션하기 위해sleep(nanoseconds:)
을 사용하는listPhoto(inGallery:)
함수의 버전이다:func listPhotos(inGallery name: String) async throws -> [String] { try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // Two seconds return ["IMG001", "IMG99", "IMG0404"] }
비동기 시퀀스 Asynchronous Sequences
이전 섹션에서는 비동기적으로 배열의 모든 요소가 준비된 후에 전체 배열을 한번에 반환했다.
또 다른 접근 방식은 비동기 시퀀스(asynchoronous sequence)를 사용하여 한번에 콜렉션의 한 요소를 기다리는 것이다.
import Foundation
let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
print(line)
}
for
- await
- in
루프는 다음 요소를 사용할 수 있을 때까지 기다리고 각 반복이 시작될 때 잠재적으로 실행을 일시 중단한다.
비동기 함수 병렬로 호출 Calling Asynchronous Functions in Parallel
await
을 사용하여 비동기 함수를 호출하면 한번에 코드의 한 부분만 실행된다.
비동기 코드가 실행되는 동안 호출자는 코드의 다음 라인을 실행하기 위해 이동하기 전에 해당 코드가 완료될 때까지 기다린다.
예를 들어 다음 예제에서는 downloadPhoto(named:)
함수에 대해 세 번의 호출을 기다린다.
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)
이 방식으로 할 경우, 다운로드가 비동기이고 진행되는 동안 다른 작업을 수행할 수 있지만 downloadPhoto(named:)
에 대한 호출은 한번에 하나만 실행되는 단점이 있다.
비동기 함수를 호출하고 주변의 코드와 병렬로 실행하려면 상수를 정의할 때 let
앞에 async
를 작성하고 상수를 사용할 때마다 await
을 작성한다.
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)
이 방식에서는 downloadPhoto(named:)
을 호출하는 세 가지는 모두 이전 호출이 완료되길 기다리지 않고 시작된다. 코드가 함수의 결과를 기다리기 위해 일시 중단되지 않기 때문에 await
로 표시하지 않고, photos
가 정의된 라인까지 계속 실행된다.
두 접근의 차이점:
- 다음 줄의 코드가 해당 함수의 결과에 따라 달라지면
await
를 사용하여 비동기 함수를 호출한다. 이는 순차적으로 실행되는 작업을 생성한다. - 나중에 코드에서 결과가 필요하지 않을 때
async
-let
을 사용하여 비동기 함수를 호출한다. 이렇게 하면 병렬로 수행할 수 있는 작업이 생성된다. await
과async
-let
은 모두 일시 중단되는 동안 다른 코드를 실행할 수 있도록 한다.- 두 경우 모두 비동기 함수가 반환될 때까지 필요한 경우 실행이 일시 중단됨을 나타내기 위해 가능한 일시 중단 지점을
await
로 표시한다.
작업과 작업 그룹 Tasks and Task Groups
작업이란?
- 프로그램의 일부로 비동기적으로 실행할 수 있는 작업 단위
- 계층 구조로 정렬됨
- 구조적 동시성 structured concurrency
- 작업 간의 명시적 부모-자식 관계를 통해 취소 전파(propagating cancellation)와 같은 일부 동작을 처리할 수 있음
구조화되지 않은 동시성 Unstructured Concurrency
구조화되지 않은 작업
- 상위 작업이 없다.
- 프로그램이 필요한 방식으로 관리할 수 있다. - 완전한 유연성
- 완전한 정확성을 갖춰야 한다.
- 현재 행위자(actor)에서 구.않.작. 생성:
Task.init(priority:operation:)
초기화 구문 호출 - 현재 행위자의 일부가 아닌 구.않.작. 생성:
Task.detached(priority:operation:)
클래스 메소드 호출 - 작업과 상호작용할 수 있는 작업 핸들(task handle)을 반환한다. -> 결과를 기다리거나 취소
let newPhoto = // ... some photo data ...
let handle = Task {
return await add(newPhoto, toGalleryNamed: "Spring Adventures")
}
let result = await handle.value
작업 취소 Task Cancellation
Swift 동시성은 협동 취소 모델(cooperative cancellation model)을 사용한다.
CancellationErro
와 같은 에러 발생nil
또는 빈 콜렉션 반환- 부분적으로 완료된 작업 반환
취소를 확인
Task.checkCancellation()
을 호출하거나Task.isCancelled
의 값을 확인하고 자체 코드에서 취소를 처리함
취소를 수동으로 전파하려면 Task.cancel()
을 호출해야 함
행위자 Actors
- 참조 타입
- (클래스와 다르게) 한 번에 하나의 작업만 변경 가능한 상태에 접근하도록 허용하므로
- 여러 작업의 코드가 행위자의 동일한 인스턴스와 상호작용하는 것은 안전하다
온도를 기록하는 행위자 예제
actor TemperatureLogger {
let label: String
var measurements: [Int]
private(set) var max: Int
init(label: String, measurement: Int) {
self.label = label
self.measurements = [measurement]
self.max = measurement
}
}
Author And Source
이 문제에 관하여(Swift - 18. 동시성 Concurrency), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@ziuge/Swift-18.-동시성-Concurrency저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)