Posts Grand Central Dispatch(GCD)
Post
Cancel

Grand Central Dispatch(GCD)

Threads

ThreadλŠ” μ •ν™•νžˆλŠ” thread of execution은 μ€„μž„λ§ μž…λ‹ˆλ‹€. ν•œ task(μž‘μ—…)의 μžμ›μ„ μ‹œμŠ€ν…œμ—μ„œ μ–΄λ–»κ²Œ μ‹€ν–‰μ‹œν‚€λŠ”μ§€λ₯Ό λœ»ν•˜κ³  있죠. μ‹€μ œλ‘œ μš°λ¦¬κ°€ μ‚¬μš©ν•˜λŠ” app에선 μ—¬λŸ¬ μž‘μ—…(multiple tasks) 이 multiple thread둜 λ™μž‘ν•©λ‹ˆλ‹€.
이런 multithreading둜 μž‘μ—…ν–ˆμ„ 경우 λ§Žμ€ μž₯점이 μžˆμŠ΅λ‹ˆλ‹€.

  • Faster execution: concurrentlyν•˜κ²Œ μž‘μ—…λ˜κΈ° λ•Œλ¬Έμ— λΉ λ₯΄κ²Œ μž‘μ—…μ΄ κ°€λŠ₯
  • Responsiveness: UIλŠ” main threadμ—μ„œλ§Œ μž‘μ—… 되기 λ•Œλ¬Έμ—, μ—¬λŸ¬ μž‘μ—…μ„ ν•Ÿλ”λΌλ„ app λ°˜μ‘μ— 영ν–₯이 μ—†λ‹€.
  • Optimized resource consumption: OS에 μ΅œμ ν™” λ˜μ–΄ μžˆλ‹€.

ν•˜μ§€λ§Œ, μ•„λž˜μ™€ 같은 지과 같이 μš°λ¦¬κ°€ 직접 OS의 threadλ₯Ό 직접 λ§Œλ“€μ–΄μ„œ μ‚¬μš©ν•˜κ²Œ λœλ‹€λ©΄ μ„±λŠ₯은 λ‹€ μ•ˆμ’‹μ•„ μ§€λŠ”κ±Έ λŠλ‚„ 수 μžˆμŠ΅λ‹ˆλ‹€.

thread

제λͺ©: λ‚΄κ°€ λ§Œλ“  multi thread


Dispatch queues

μœ„μ™€ 같은 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ DispatchQueueλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
DispatchQueueλŠ” 직접 threadλ₯Ό λ§Œλ“œλŠ” 것이 μ•„λ‹Œ OSμ—κ²Œ threadλ₯Ό λ§Œλ“€ queueλ₯Ό μ œκ³΅ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. κ°„λ‹¨ν•˜κ²Œ λ§ν•˜λ©΄ OS에 직접 threadλ₯Ό λ§Œλ“œλŠ” 것이 μ•„λ‹ˆκ³  λ§Œλ“€μ–΄μ•Όν•  threadλ₯Ό queue에 λ„£μ–΄ OSμ—μ„œ threadλ₯Ό μ΅œμ ν™”ν•΄μ„œ λ§Œλ“€ 도둝 ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

μ½”λ“œλŠ” κ°„λ‹¨ν•©λ‹ˆλ‹€.

1
2
let label = "com.grohong.dispatch"
let queue = DispatchQueue(label: label)

μ΄λ ‡κ²Œ DispatchQueue에 κ°„λ‹¨ν•œ 라벨링으둜 μƒμ„±ν•œλ‹€λ©΄, 후에 ν•΄λ‹Ή 라벨둜 queueλ₯Ό μž¬μ‚¬μš© ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


The main queue

App μ‹€ν–‰ν•˜κ²Œ 되면 main dispatch queueκ°€ λ§Œλ“€μ–΄ μ§‘λ‹ˆλ‹€. ν•΄λ‹Ή queue의 serial queueμ—μ„œλŠ” UI μž‘μ—…μ΄ 순차적으둜 μΌμ–΄λ‚©λ‹ˆλ‹€. κ·ΈλŸ¬λ‹ˆ ν•΄λ‹Ή queue에 μž‘μ—…μ„ λ„£μœΌλ©΄ μ‚¬μš©μžμ˜ UI λ°˜μ‘μ— 영ν–₯이 κ°€κΈ° λ•Œλ¬Έμ—, μ„±λŠ₯이 μ €ν•˜λ˜λŠ” κ±Έ 체감할 수 μžˆμŠ΅λ‹ˆλ‹€.

DispatchQueue.main.sync { } μž‘μ—…μ€ ν•˜λ©΄ μ•ˆλœλ‹€!


Quality of service

DispatchQueueλŠ” serial λ˜λŠ” concurrentν•˜κ²Œ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.
concurrentν•œ DispatchQueue도 κ°„λ‹¨ν•˜κ²Œ 생성 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

1
2
let label = "com.grohong.dispatch.concurrent"
let queue = DispatchQueue(label: label, attributes: .concurrent)

μ΄λ•Œ, DispatchQueue의 Quality of service(QoS)λ₯Ό μ„€μ •ν•˜μ—¬ μš°μ„ μˆœμœ„λ₯Ό μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
UI와 μƒκ΄€μ—†λŠ” μž‘μ—…μ„ μš°μ„ μˆœμœ„λ₯Ό μ •ν•˜μ—¬ μ‚¬μš©ν•˜κ³  μ‹Άμ€κ²½μš° μ•„λž˜μ™€ 같이 global queue에 QoSλ₯Ό λ§Œλ“€μ–΄ λ„£μ–΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

Global queuesλŠ” 항상 concurrent 이고 FIFO(first in, firsst out) μž…λ‹ˆλ‹€.

1
let queue = DispatchQueue.global(qos: .userInteractive)

κ°œλ³„ DispatchQueue도 λ‹€μŒκ³Ό 같이 κ°„λ‹¨ν•˜κ²Œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

1
2
3
let queue = DispatchQueue(label: label, 
                          qos: .userInitiated,
                          attributes: .concurrent)

QoSλŠ” 6κ°€μ§€λ‘œ λ‚˜λ‰˜κ²Œ λ©λ‹ˆλ‹€.

.userInteractive

userInteractiveλŠ” κ°€μž₯ 높은 μˆœμœ„λ‘œ UI λ°˜μ‘κ³Ό λ™μ‹œμ— λΉ λ₯΄κ²Œ λ°˜μ‘ν•  μž‘μ—…μ„ λ„£μ–΄μ€λ‹ˆλ‹€.

.userInitiated

.userInitiatedλŠ” μ‚¬μš©μžμ˜ λ°˜μ‘κ³Ό λ™μ‹œμ— μ‹œμž‘ν•˜μ§€λ§Œ, λΉ„λ™κΈ°μ μœΌλ‘œ 처리될 μž‘μ—…μ„ λ„£μ–΄μ€λ‹ˆλ‹€.

.utility

μœ„μ˜ QoSλŠ” μ„±λŠ₯이 μ€‘μš”ν•œ μž‘μ—…μ΄λΌλ©΄, .utilityλŠ” I/O, networking κ³Ό data feed λ“± κΈ‰ν•˜μ§€ μ•Šμ€ 일을 μ—λ„ˆμ§€ 효율과 μ„±λŠ₯을 λ™μ‹œμ— μ‹ κ²½ μ“°λŠ” μž‘μ—…μ„ λ„£μ–΄μ€λ‹ˆλ‹€.

.background

.backgroundλŠ” μ‚¬μš©μžκ°€ λˆˆμΉ˜μ±„μ§€ λͺ»ν•  μ •λ„μ˜ μž‘μ—…μ„ λ„£μ–΄ μ€λ‹ˆλ‹€.

.default and .unspecified

μš°μ„ μˆœμœ„λ₯Ό μ•ˆμ •ν•΄μ€€ default value이고, unspecified 거의 μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” λ‹¨κ³„μ˜ μž‘μ—…μ„ λœ»ν•©λ‹ˆλ‹€.


Adding task to queues

Dispatch queuesλŠ” task에 methodλ₯Ό sync λ˜λŠ” async ν•˜κ²Œ μ²˜λ¦¬ν•˜λ„λ‘ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
κ°„λ‹¨ν•œ async methodλ₯Ό μΆ”κ°€ν•˜λŠ” μ½”λ“œλ₯Ό λ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
DispatchQueue.global(qos: .utility).async { [weak self] in
  guard let self = self else { return }

  // Perform your work here
  // ...

  // Switch back to the main queue to
  // update your UI
  DispatchQueue.main.async {
    self.textLabel.text = "New articles available!"
  }
}

μ—¬κΈ°μ„œ μ£Όμ˜ν•  점은 UI의 μ—…λ°μ΄νŠΈλŠ” 항상 main queueμ—μ„œ 이뀄져야 ν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.


DispatchGroup

DispatchGroup은 class둜 각각의 queue의 tasksλ₯Ό group으둜 λ¬Άμ–΄ 관리할 수 μžˆμŠ΅λ‹ˆλ‹€.

1
2
3
4
5
6
7
8
9
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"
}

μœ„μ— μ½”λ“œμ—μ„œ λ³Ό 수 μžˆλ“―μ΄, 각각의 queue에 group을 μ„€μ • ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
DispatchGroups은 notify(queue: )μ œκ³΅ν•©λ‹ˆλ‹€. ν•΄λ‹Ή ν•¨μˆ˜λŠ” group에 λͺ¨λ“  taskκ°€ λͺ¨λ“œ λλ‚¬μ„λ•Œ μ•Œλ €μ€λ‹ˆλ‹€.


Synchronous waiting

λ§Œμ•½ DispatchGroupμ—μ„œ taskκ°€ λλ‚˜μ§€ μ•Šμ•„ notify(queue: )λ₯Ό 받지 λͺ»ν•˜λŠ” κ²½μš°κ°€ 생길 수 μžˆμŠ΅λ‹ˆλ‹€.
이럴 경우 wait(timeout) methodλ₯Ό μ‚¬μš©ν•˜μ—¬ .timeOut 을 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)

queue.async(group: group) {
  print("Start job 1")
  Thread.sleep(until: Date().addingTimeInterval(10))
  print("End job 1")
}

queue.async(group: group) {
  print("Start job 2")
  Thread.sleep(until: Date().addingTimeInterval(2))
  print("End job 2")
}

if group.wait(timeout: .now() + 5) == .timedOut {
  print("I got tired of waiting")
} else {
  print("All the jobs have completed")
}

μœ„ μ½”λ“œλ₯Ό 보면, wait(timeout)에 5초의 timeOutλ₯Ό μ£Όμ–΄ λ§Œμ•½ timeout νŒŒλΌλ―Έν„° μ‹œκ°„λ‚΄μ— taskκ°€ λλ‚˜μ§€ μ•ŠλŠ”λ‹€λ©΄ μ‘°κ±΄λ¬Έμ—μ„œ μ˜ˆμ™Έμ²˜λ¦¬κ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€.


Wrapping asynchronous methods

DispatchGroupμ—μ„œλŠ” μœ„μ— 예제 처럼 queue의 taskκ°€ λλ‚ κ²½μš° group에 notiλ₯Ό μ€λ‹ˆλ‹€. ν•˜μ§€λ§Œ queue의 task λ‚΄λΆ€μ˜ μž‘μ—…μ΄ asyncν•˜κ²Œ 끝날 κ²½μš°μ—λŠ” taskκ°€ λ‹€ λλ‚˜μ§€ μ•Šμ€ μƒν™©μ—μ„œ notiκ°€ κ°€λŠ” κ²½μš°κ°€ 생길 κ²ƒμž…λ‹ˆλ‹€.
μ΄λŸ΄λ•Œ DispatchGroup의 enter 와 leaveλ₯Ό μ΄μš©ν•˜μ—¬ ν•΄κ²° ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
queue.dispatch(group: group) {
  // count is 1
  group.enter()
  // count is 2
  someAsyncMethod { 
    defer { group.leave() }
    
    // Perform your work here,
    // count goes back to 1 once complete
  }
}

μœ„μ˜ μ½”λ“œ 처럼 taskκ°€ μ‹œμž‘λ λ•Œ enter()λ₯Ό ν˜ΈμΆœν•˜μ—¬ DispatchGroup에 λ“€μ–΄κ°€λŠ” 것을 μ•Œλ¦¬κ³ , asyncν•œ taskκ°€ 끝날 경우 deferλ₯Ό μ΄μš©ν•˜μ—¬ λ§ˆμ§€λ§‰μ— taskκ°€ 마무리 λμŒμ„ leave() μ•Œλ €μ£Όλ©΄ λ©λ‹ˆλ‹€.


Semaphores

Semaphore을 μ΄μš©ν•˜λ©΄ task에 μ‚¬μš©λ˜λŠ” λ¦¬μ†ŒμŠ€λ₯Ό 관리할 수 μžˆμŠ΅λ‹ˆλ‹€.
μ•„λž˜ μ½”λ“œ 처럼 DispatchSemaphoreλ₯Ό μƒμ„±ν• λ•Œ μ‚¬μš©ν•  λ¦¬μ†Œλ₯Ό μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

1
let semaphore = DispatchSemaphore(value: 4)

ν•΄λ‹Ή DispatchSemaphoreλŠ” waitλ₯Ό μ΄μš©ν•΄ λ¦¬μ†ŒμŠ€λ₯Ό μ€€λΉ„ν•˜κ³ , signalλ₯Ό μ΄μš©ν•˜μ—¬ λ¦¬μ†ŒμŠ€ ν•΄μ œ νƒ€μž„μ„ μ‘°μ ˆν•  수 μžˆμŠ΅λ‹ˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInteractive)
let semaphore = DispatchSemaphore(value: 1)

let base = "https://wolverine.raywenderlich.com/books/con/image-from-rawpixel-id-"
let ids = [466881, 466910, 466925, 466931, 466978, 467028, 467032, 467042, 467052]

var images: [UIImage] = []

for id in ids {
    guard let url = URL(string: "\(base)\(id)-jpeg.jpg") else { continue }

    semaphore.wait()
    group.enter()

    let task = URLSession.shared.dataTask(with: url) { data, _, error in
        defer {
            print("Finish download \(id)")
            group.leave()
            semaphore.signal()
        }

        if error == nil,
           let data = data,
           let image = UIImage(data: data) {
            images.append(image)
        }
    }

    task.resume()
}

// Finish download 466881
// Finish download 466910
// Finish download 466925
// Finish download 466931
// Finish download 466978
// Finish download 467028
// Finish download 467032
// Finish download 467042
// Finish download 467052

μœ„μ˜ 예제λ₯Ό λ³Έλ‹€λ©΄, DispatchSemaphore의 λ¦¬μ†ŒμŠ€κ°€ ν•˜λ‚˜μ΄κΈ° λ•Œλ¬Έμ— 이미지가 downloadκ°€ 순차적으둜 μ΄λ€„μ§€λŠ”κ±Έ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
ν•˜μ§€λ§Œ λ¦¬μ†ŒμŠ€λ₯Ό 4개둜 λŠ˜λ Έμ„ 경우 λ¦¬μ†ŒμŠ€μ˜ taskκ°€ λλ‚˜λŠ” 타이밍에 따라 λ‹€μš΄λ‘œλ“œκ°€ λ‹€μš΄μ΄ 이루어 μ§‘λ‹ˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
let semaphore = DispatchSemaphore(value: 4)

// Finish download 466881
// Finish download 466910
// Finish download 466931
// Finish download 467028
// Finish download 467032
// Finish download 467042
// Finish download 466925
// Finish download 466978
// Finish download 467052

// Always change....
This post is licensed under CC BY 4.0 by the author.

읽기 쒋은 μ½”λ“œκ°€ 쒋은 μ½”λ“œλ‹€(3)

What's new in RxSwift6(1)