Chapter 4: Groups & Semaphores
- 때로는 작업을 대기열에 넣는 대신 작업 그룹을 처리해야 하는 경우가 있습니다.
- 모두 동시에 실행할 필요는 없지만 모두 완료한 시점을 알아야 합니다.
- Apple은 이 정확한 시나리오를 위해 Dispatch Groups을 제공합니다.
DispatchGroup
- 적절하게 이름이 지정된
DispatchGroup
클래스는 작업 그룹의 완료를 추적 할 때 사용하는 클래스입니다.
DispatchGroup
을 초기화하여 시작하십시오. 하나가 있고 해당 그룹의 일부로 작업을 추적하려면 디스패치 큐의 비동기 메소드에 인수로 그룹을 제공 할 수 있습니다.
let group = DispatchGroup()
someQueue.async(group: group) { ... your work ... }
someQueue.async(group: group) { ... more work .... }
someOtherQueue.async(group: group) { ... other work ... }
group.notify(queue: DispatchQueue.main) { [weak self] in
self?.textLabel.text = "All jobs have completed"
}
- 위의 예제 코드에서 볼 수 있듯이 그룹은 단일 디스패치 큐에 고정되지 않습니다.
- 실행해야 하는 작업의 우선 순위에 따라 단일 그룹을 사용하면서 여러 대기열에 작업을 제출할 수 있습니다.
DispatchGroup
은notify(queue:)
메소드를 제공하며, 제출 된 모든 작업이 완료 되자마자 통지 할 수 있습니다.
- notify 메소드는 디스패치 큐를 매개 변수로 사용합니다.
- 작업이 모두 완료되면 제공 한 마감이 표시된 발송 대기열에서 실행됩니다.
- 표시되는 알림 호출은 가장 자주 사용하는 버전 일 수 있지만 서비스 품질()을 지정할 수있는 다른 버전도 있습니다.
Synchronous waiting
- 어떤 이유로 그룹의 완료 알림에 비동기 적으로 응답 할 수 없는 경우 대신 디스패치 그룹에서 대기 메소드를 사용할 수 있습니다.
- 모든 작업이 완료 될 때까지 현재 대기열을 차단하는 동기식 방법입니다.
- 작업이 완료 될 때까지 대기하는 시간을 지정하는 선택적 매개 변수가 필요합니다.
- 지정되지 않은 경우 무한 대기 시간이 있습니다.
let group = DispatchGroup()
someQueue.async(group: group) { ... }
someQueue.async(group: group) { ... }
someOtherQueue.async(group: group) { ... }
if group.wait(timeout: .now() + 60) == .timedOut {
print("The jobs didn’t finish in 60 seconds")
}
Wrapping asynchronous methods
queue.dispatch(group: group) {
// count is 1
group.enter()
// count is 2
someAsyncMethod {
defer { group.leave() }
// 여기서 작업을 수행하십시오.
// 완료되면 카운트가 1로 돌아갑니다.
}
}
- 위의 코드 샘플과 비슷한 간단한 경우 enter / leave 쌍을 직접 호출하면됩니다.
- 디스패치 그룹과 함께 someAsyncMethod를 자주 사용하려는 경우 필요한 호출을 잊지 않도록 메소드를 랩핑해야합니다.
func myAsyncAdd(
lhs: Int,
rhs: Int,
completion: @escaping (Int) -> Void
) {
// Lots of cool code here
completion(lhs + rhs)
}
func myAsyncAddForGroups(
group: DispatchGroup,
lhs: Int,
rhs: Int,
completion: @escaping (Int) -> Void
) {
group.enter()
myAsyncAdd(first: first, second: second) { result in
defer { group.leave() }
completion(result)
}
}
- 래퍼 메서드는 그룹에 대해 계산할 매개 변수를 취한 다음 나머지 인수는 래핑하는 메서드의 인수와 정확히 동일해야합니다.
- 그룹의 출입 방법이 올바르게 처리되도록 100 % 확신하는 것 외에 비동기 방법을 포장하는 데 특별한 것은 없습니다.
- 랩퍼 메소드를 작성하면 테스트를 수행 할 수 있습니다.
- 모든 활용에서 적절한 발신 및 발신 통화 쌍을 확인하기 위해 단일 위치로 단순화되었습니다.
Downloading Images
- 귀하의 작업은 제공된 이름 배열에서 각 이미지를 비동기 방식으로 다운로드하는 것입니다.
- 완료되면 하나 이상의 이미지를 표시하고 놀이터를 종료해야합니다.
for id in ids {
guard let url = URL(string: "\(base)\(id)-jpeg.jpg") else { continue }
group.enter()
let task = URLSession.shared.dataTask(with: url) { data, _, error in
defer { group.leave() }
if error == nil,
let data = data,
let image = UIImage(data: data) {
images.append(image)
}
}
task.resume()
}
- enter와 leave 쌍을 올바르게 처리하기 때문에 더 이상 그룹이 들어올 때까지 회전하고 동기적으로 기다릴 필요가 없습니다.
- 모든 이미지 다운로드가 완료되면
notify(queue :)
콜백 메소드를 사용하십시오.
- for 루프 외부에 이 코드를 추가하십시오.
group.notify(queue: queue) { images[0]
PlaygroundPage.current.finishExecution()
}
- 지금 playground를 실행하고 사이드 바를 보면 각 작업이 시작되고 이미지가 다운로드되고 결국 첫 번째 이미지로 알림이 트리거되는 것을 볼 수 있습니다.
Semaphores (신호장치)
- 공유 리소스에 액세스 할 수 있는 스레드 수를 실제로 제어해야 하는 경우가 있습니다.
- 단일 스레드에 대한 액세스를 제한하는 읽기 / 쓰기 패턴을 이미 보았지만 전체 스레드 수를 계속 제어하면서 한 번에 더 많은 리소스를 사용할 수있는 경우가 있습니다.
- 예를 들어 네트워크에서 데이터를 다운로드하는 경우 한 번에 다운로드 횟수를 제한 할 수 있습니다.
- 디스패치 대기열을 사용하여 작업을 처리하고 디스패치 그룹을 사용하여 모든 다운로드가 완료된 시기를 알 수 있습니다.
- 그러나 수신하는 데이터가 상당히 크고 처리하기에 무겁기 때문에 한 번에 네 번의 다운로드 만 허용하려고 합니다.
DispatchSemaphore
를 사용하면 해당 사용 사례를 정확하게 처리 할 수 있습니다.
- 원하는 자원을 사용하기 전에, 단순히 동기 함수 인
wait
메소드를 호출하면 자원이 사용 가능할 때까지 스레드가 실행을 일시 정지합니다.
- 아직 소유권을 주장한 것이 없으면 즉시 액세스 할 수 있습니다.
- 다른 쪽이 가지고 있다면, 자신이 끝났다는 신호를 보낼 때까지 기다립니다.
- 세마포어를 작성할 때 자원에 대한 동시 액세스 수를 지정하십시오.
- 네 번의 네트워크 다운로드를 한 번에 활성화하려면 4를 전달하십시오.
- 독점 액세스를 위해 리소스를 잠그려면 1 만 지정하면 됩니다.
- 디스패치 큐를 지정하는 행 다음에 네 개의 동시 액세스를 허용하는 세마포어를 작성하십시오.
- 10 번의 네트워크 다운로드 수행을 시뮬레이션하고 그룹을 사용하여 큐에 디스패치하는 루프를 작성하십시오.
- 세마포어를 만든 직후 루프를 구현하십시오.
let semaphore = DispatchSemaphore(value: 4)
for i in 1...10 {
queue.async(group: group) {
semaphore.wait()
defer { semaphore.signal() }
print("Downloading image \(i)")
// Simulate a network wait
Thread.sleep(forTimeInterval: 3)
print("Downloaded image \(i)")
}
}
- 디스패치 그룹에서
leave()
를 호출해야 했던 것처럼 리소스 사용이 완료되면signal()
를 보내야합니다.
- 리소스를 사용하지 않고 떠날 수있는 방법이 없으므로
defer
블록을 사용하는 것이 가장 좋습니다.
- playground를 실행하면 즉시 4 번의 다운로드가 발생하고 3 초 후에 또 다른 4 번이 발생한다는 것을 즉시 확인할 수 있습니다.
- 마지막으로 3 초 후에 마지막 2번이 완료됩니다.
Semaphore added to Downloading Images
- 제어하려는 리소스는 네트워크이므로 URL을 for 루프에서 만들 수 있지만 그룹에 들어가기 전에 사용 가능한 세마포어를 기다려야하므로
group.enter()
전에 세마포어 호출 만 추가하면됩니다.
- 세마포어가 자원에 대한 액세스를 제어하고 디스패치 그룹은 완료를 추적하는 방법이므로 두 요소를 모두 사용해야 합니다.
- 세마포어의 릴리스를 처리하도록
defer
명령문을 수정하십시오.
...
group.enter()
semaphore.wait()
let task = URLSession.shared.dataTask(with: url) { data, _, error in
defer {
group.leave()
semaphore.signal()
}
...
- 줄의 순서는 실제로 중요하지 않습니다.
- 나는 단지 세마포어가 작업을 시작하고 끝내는 외부 요소가 되도록 하고 싶습니다.
- 세마포어 자체를 어떤 유형의 자원으로 생각하십시오.
- 세 개의 망치와 네 개의 톱을 사용할 수있는 경우 두 개의 세마포어를 만들어 이를 나타냅니다.
let hammer = DispatchSemaphore(value: 3)
let saw = DispatchSemaphore(value: 4)
댓글