Jinsujin / translate-documents

iOS 연관 문서 번역
0 stars 0 forks source link

Concurrency #3

Open Jinsujin opened 1 year ago

Jinsujin commented 1 year ago

Concurrency

SWIFT 5.7

Swift 는 비동기와(asynchronous) parallel(병렬) 코드를 구조화된 방식으로 작성할 수 있게끔 기본적으로(built-in) 지원한다. 비동기 코드는 일시중지(suspended)나 나중에 실행할 수 있지만, 한번에 프로그램의 한 부분만 실행된다. 프로그램에서 코드를 일시중지나 다시실행하면, (네트워크를 통해 data를 fetching 하거나, 파일을 parsing하는 등)장기적인 작업을 계속 실행 하면서 UI 업데이트와 같은 단기적인 작업을 계속 진행할 수 있다.

프로그램에서 코드를 연기(Suspending)하거나 다시실행(resuming)하면, (네트워크를 통해 data를 fetching 하거나, 파일을 parsing하는 등)장기적인 작업을 계속 실행 하면서 UI 업데이트와 같은 단기적인 작업을 계속 진행할 수 있다.

Parallel code 는 여러개의 코드를 동시에 실행하는 것을 의미한다. — 예를들어, 4코어 프로세서 컴퓨터는 동시에 4개의 코드를 실행할 수 있으며, 각 코어는 task 중 하나를 수행한다. parallel 과 비동기(asynchronous) 코드를 사용하는 프로그램은 한번에 여러개의 작업을 수행한다; 외부 시스템(external system)을 기다리는 작업을 연기(suspend)하고, memory-safe 한 방식으로 코드를 더 쉽게 작성할 수 있다. parallel 이나 비동기(asynchronous) 코드에서의 추가적인 스케쥴링 유연성은 복잡성 증가의 비용이 발생한다. Swift 는 당신의 의도를 표현할 수 있게 하는 방식을 제공한다. 그 방식은 compile-time checking 을 가능하게 한다. 예를들어 actor 를 사용해 mutable state 에 안전하게 접근할 수 있다. 그러나 버그가 많은 코드에 concurrency 를 추가한다고 해서 빠르거나 정확해진다는 보장은 없다. 사실, concurrency 를 추가하는 것은 당신의 코드를 디버깅 하기 더 어려워질 수 있다. concurrency 를 지원하는 Swift language-level 을 사용하면, Swift 가 컴파일 시점에 생긴 문제를 파악하는데 도움이 된다. 이 챕터의 나머지 부분에서는 비동기와 parallel(병렬) 코드의 일반적인 조합을 concurrency 라는 용어를 사용한다.

Note ❗️

당신이 이전에 concurrent 코드를 작성해봤다면, 스레드 작업에 익숙할지도 모른다. concurrency model 은 thread 위에 구축되지만, 당신은 스레드와 직접 상호작용하지 않는다. Swift 에서의 비동기 함수는 실행중인 스레드를 포기할 수 있고, 첫번째 함수가 block 된 동안 다른 비동기 함수가 해당 스레드에서 실행할 수 있다. 비동기 함수가 다시 시작될때(resume), Swift 는 해당 함수가 실행될 스레드에 대해 보증하지 않는다.

Swift 의 언어 지원을 사용하지 않고 concurrent(동시) 코드를 작성하는 것은 가능하지만, 코드는 더 읽기 어려울 수 있다. 예를들어, 아래의 코드는 photo name 리스트를 다운로드 하고, list 의 첫번째 사진을 다운로드 한 다음 사용자에게 해당 사진을 표시한다.

listPhotos(inGallery: "Summer Vacation") { photoNames in
    let sortedNames = photoNames.sorted()
    let name = sortedNames[0]
    downloadPhoto(named: name) { photo in
        show(photo)
    }
}

이런 간단한 경우에도, 코드를 completion handler 로 작성해야 하므로, 중첩된 클로저를 쓰게된다. 이런 스타일에서는, 깊은 중첩이 있는 복잡한 코드는 다루기 어려울 수 있다.

Jinsujin commented 1 year ago

앞으로 볼 WWDC 목록

Swift 비동기 & 동시실행 시작하기

  • [ ] WWDC21 Meet async/await in Swift
  • [ ] WWDC21 Discover concurrency in SwiftUI
  • [ ] WWDC21 Use async/await with URLSession
  • [ ] WWDC22 Swift 의 분산된 Actor 소개
  • [ ] WWDC22 Swift Concurrency 시각화 및 최적화
  • [ ] WWDC22 Swift Async 알고리즘 소개
  • [ ] WWDC21 Swift concurrency: Update a sample app
  • [ ] WWDC21 Swift concurrency: Behind the scenes
  • [ ] WWDC21 Protect mutable state with Swift actors
  • [ ] WWDC21 Explore structured concurrency in Swift
  • [ ] WWDC22 Swift Concurrency 를 사용하여 데이터 경합 제거
Jinsujin commented 1 year ago

Defining and Calling Asynchronous Functions

비동기 함수(asynchronous function)나 메서드(method)는 실행도중에 연기(suspend) 될 수 있는 특별한 종류의 함수나 메서드이다. 이것은 일반적인 비동기 함수나 메서드와 (완료-completion 까지 실행되거나, 에러를 던지거나, return 하지 않는) 차이를 보인다. 비동기 함수나 메서드는 이들 세가지 방법중에 하나를 수행하지만, 어떤것을 기다릴때 중간에 일시중지(pause) 할 수 있다.

비동기 함수나 메서드의 body 안에서, 당신은 연기(suspend) 할 수 있는 각 위치를 표시(mark) 한다. 함수나 메서드가 비동기라는 것을 표시(indicate)하기위해, 당신은 parameter 뒤의 선언에서 async 키워드를 작성한다. 이것은 당신이 throws 를 사용해 함수를 throwing 하는 것을 표시(mark)하는 것과 유사하다. 함수나 메서드가 값을 반환한다면, async 를 화살표(->) 앞에 작성한다. 예를들어, 갤러리에서 사진의 이름을 가져오는 방법은 아래처럼 한다:

func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}

비동기나 throwing 을 하는 함수나 메서드의 경우, throws 전에 async 를 작성한다. 비동기 메서드를 호출하면, 메서드가 return 될때까지 실행이 연기(suspend)된다. 호출의 앞에 await 를 작성하면 작업을 연기(suspension)할 수 있는 지점을 표시할 수 있다. 이것은 (error 가 있는 경우에 프로그램의 흐름에 가능한 변경사항을 표시하기 위해) throwing 함수를 호출할때 try 를 작성하는 것과 같다.

비동기 메서드 내부에서, 실행의 흐름은 당신이 다른 비동기 메서드를 호출할때만 연기(suspend)된다. —연기는 절대 암시적(implicit)이거나 선제적(preemptive)이지 않음— 이는 모든 가능한 연기할 수 있는 지점(suspension point)은 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:) 함수는 모두 네트워크 request 를 만드는 것을 필요로 하기때문에, 완료하는데 비교적 긴 시간이 걸릴 수 있다. return 화살표 앞에 async 를 작성함으로서 둘다 비동기식으로 만들면, picture 가 준비될때까지 이 코드가 기다리는 동안 앱의 나머지 코드는 계속 실행된다. 위 예제의 동시성 특성을 이해하기 위해, 여기 가능한 실행순서가 있다:

  1. 코드의 실행은 첫번째 라인에서부터 시작해서 첫 await 까지 실행한다. listPhotos(inGallery:) 함수가 호출되고 해당 함수가 return 할때까지 실행이 연기된다.
  2. 이 코드의 실행이 연기(suspend)되는 동안, 같은 프로그램에 있는 다른 concurrent 코드가 실행된다. 예를들어, long-running background task 이 사진 갤러리의 리스트를 계속 업데이트 할 수 있다. 이 코드는 await 으로 표시된 다음 연기 지점(suspension point)이나, 완료될때까지 실행된다.
  3. listPhotos(inGallery:) 가 return 된 후에, 이 코드는 해당 지점부터 실행을 계속한다. 반환되어진 값이 photoNames 에 할당된다.
  4. 라인에 정의된 sortedNames 과 name 은 일반적인 동기코드다. 해당 라인에 await 표시가 없기때문에, 가능한 연기 지점(suspension point)이 없다.
  5. 다음 await 표시는 downloadPhoto(named:) 함수 호출이다. 이 코드는 함수가 return 할때까지 다시 실행을 일시정지해서, 다른 concurrent code 를 실행할 수 있는 기회를 준다.
  6. downloadPhoto(named:) return 후에, 해당 반환 값은 photo 에 할당되어진 다음, show(_:) 이 호출되면 argument 로 전달되어 진다.

await 으로 표시된 코드에서 가능한 연기지점(suspension point)은 비동기 함수나 메서드가 return 될때까지 기다리는 동안 현재 코드 조각이 실행을 일시중지(pause)할 수 있음을 나타낸다.

이는 yielding the thread (스레드 생성)이라고 불리어진다. Swift가 뒤에서 현재 스레드에서 코드 실행을 연기(suspend)하고, 대신에 해당 스레드에서 다른 코드를 실행하기 때문이다. await 가 있는 코드는 실행을 연기(suspend)할 수 있어야 하기때문에, 당신의 프로그램의 특정 위치에서만 비동기 함수나 메소드를 호출할 수 있다.:

가능한 연기지점(suspension point) 사이에 위치한 코드는 다른 동시(concurrent) 코드의 중단(interruption) 가능성 없이 순차적으로 실행된다. 예를 들어, 아래 코드는 한 갤러리에서 다른 갤러리로 사진을 이동한다:

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(_:toGallery:)remove(_:fromGallery:) 호출 사이에 다른 코드를 실행할 수 있는 방법이 없다. 그 시간동안, 첫번째 photo 가 두 갤러리 사이에 나타나며, 앱의 불변성(invariant) 중 하나를 깨뜨린다. 이 코드 덩어리가 await 하지 않아야 한다는 것을 명확히 하기 위해 나중에 추가하면, 해당 코드를 동기 function 으로 리펙터링 할 수 있다.

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(_:from:to:) 함수가 동기식이면 연기 지점(suspension point)을 포함하지 않는다는 것을 보장한다. 앞으로 이 함수에 동시성(concurrent) 코드를 추가하려고 하면, 연기 시점(suspension point)이 생길 수 있으므로, 버그를 접하는 대신 컴파일타임 에러가 발생한다.

Note ❗️

Task.sleep(until:tolerance:clock:) 메서드는 간단한 코드를 작성해서 동시성(concurrency)이 어떻게 동작하는지 배울때 유용하다. 이 메서드는 아무것도 수행하지 않지만, 반환하기 전에 적어도 주어진 나노초만큼 기다린다. 여기 sleep(until:tolerance:clock:) 을 사용해 네트워크 작업 대기를 시뮬레이션하는 listPhotos(inGallery:) 함수가 있다.

func listPhotos(inGallery name: String) async throws -> [String] {
    try await Task.sleep(until: .now + .seconds(2), clock: .continuous)
    return ["IMG001", "IMG99", "IMG0404"]
}
Jinsujin commented 1 year ago

Asynchronous Sequences

이전 section에 있는 listPhotos(inGallery:) 함수는 array 의 모든 요소가 준비된 이후에 전체 array를 한번에 비동기적으로 return 한다. 다른 접근방법은 asynchronous sequence 를 사용해 한번에 하나의 컬렉션의 요소를 기다리는 것이다.:

import Foundation

let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
    print(line)
}

일반적인 for-in loop 사용하는것 대신에, 위의 예제에서는 await 를 뒤에 작성한다. 비동기 함수나 메서드를 호출하는 것처럼, await 을 작성하여 가능한 연기 지점(suspension point)을 표시한다. for-await-in loop 는 잠재적으로 각 반복(iteration) 이 시작될때 실행을 연기(suspend) 한다. 이때, 다음 요소를 사용할 수 있기를 기다린다.

Sequence protocol 에 적합성(conformance)을 추가하여 for-in loop 에서 자신의 타입을 사용할 수 있는것과 같은 방식으로,

AsyncSequence protocol 에 적합성을 추가하여 for-await-in loop 에서 자신의 유형을 사용할 수 있다.