정보

GCD(Grand Central Dispatch)의 주요 개념과 사용 방법을 설명해주세요.

밤새는 탐험가89 2024. 11. 20. 03:15

GCD(Grand Central Dispatch)는 Apple의 멀티스레딩 및 동시성 처리를 위한 프레임워크로, 효율적인 작업 분배와 스레드 관리를 가능하게 합니다. GCD를 사용하면 비동기 작업을 손쉽게 관리할 수 있으며, 애플리케이션의 성능을 향상시키는 데 유용합니다.

 

 

주요 개념

  1. 디스패치 큐 (Dispatch Queue)
    • 작업을 실행할 큐를 관리하며, 작업의 실행 순서를 정의합니다.
    • 종류:
      • Serial Queue: 작업을 순차적으로 실행합니다. 하나의 작업이 끝나야 다음 작업을 시작합니다.
      • Concurrent Queue: 작업을 동시에 실행합니다. 실행 순서는 보장되지 않지만, 각 작업은 병렬적으로 실행됩니다.
      • Main Queue: 메인 스레드에서 실행되는 Serial Queue입니다. UI 업데이트는 반드시 Main Queue에서 실행해야 합니다.
  2. 동기/비동기 실행
    • 동기 실행 (sync): 호출한 스레드에서 작업을 즉시 실행하고 완료될 때까지 기다립니다.
    • 비동기 실행 (async): 호출한 스레드에서 작업을 큐에 추가하고 즉시 반환합니다. 작업 완료 여부와 관계없이 호출자는 계속 실행됩니다.
  3. QoS (Quality of Service)
    • 작업의 중요도를 정의하며, 시스템이 우선 순위를 조정합니다.
      • User-interactive: UI와 관련된 작업 (가장 높은 우선순위)
      • User-initiated: 사용자가 요청한 작업
      • Utility: 백그라운드 처리 작업
      • Background: 사용자에게 보이지 않는 작업 (가장 낮은 우선순위)
  4. Work Item
    • 큐에 추가할 작업 단위로, 클로저(closure)를 사용하여 정의합니다.

 

GCD 사용 방법: 종류별 자세한 설명과 예제

 

1. Global Queue

Global Queue는 시스템에서 제공하는 Concurrent Queue입니다. DispatchQueue.global(qos:)를 통해 접근할 수 있으며, 동시성 작업을 간단하게 실행할 때 유용합니다.
QoS(Quality of Service)를 설정하여 작업의 우선순위를 조정할 수 있습니다.

DispatchQueue.global(qos: .userInitiated).async {
    // 이미지 다운로드 (비동기)
    if let url = URL(string: "https://example.com/image.jpg"),
       let data = try? Data(contentsOf: url),
       let image = UIImage(data: data) {
        DispatchQueue.main.async {
            // UI 업데이트는 Main Queue에서 처리
            self.imageView.image = image
        }
    }
}

 

  • DispatchQueue.global(qos: .userInitiated)는 사용자 요청에 따라 이미지를 다운로드합니다.
  • 다운로드가 완료되면 Main Queue에서 UI를 업데이트합니다.

 

2. Serial Queue

Serial Queue는 작업을 하나씩 순차적으로 실행합니다. 동시에 실행되지 않으므로 작업의 순서를 보장할 수 있습니다.
사용자가 직접 Serial Queue를 생성하여 특정 작업의 실행 순서를 제어할 수 있습니다.

let fileQueue = DispatchQueue(label: "com.example.fileQueue") // Serial Queue 생성

fileQueue.async {
    print("Step 1: 파일 열기")
    sleep(1)
    print("Step 2: 데이터 쓰기")
    sleep(1)
    print("Step 3: 파일 닫기")
}

 

  • Serial Queue는 작업을 순서대로 실행하므로 파일이 정상적으로 열리고, 데이터를 쓰고, 닫는 순서가 유지됩니다.
  • 작업 간 충돌 없이 안전하게 실행됩니다.

 

3. Main Queue

Main Queue는 UI 업데이트 및 사용자와의 상호작용에 사용됩니다.
Main Queue는 Serial Queue이며, 메인 스레드에서 실행되기 때문에 반드시 UI 관련 작업은 Main Queue에서 처리해야 합니다.

@IBAction func loadDataButtonTapped(_ sender: UIButton) {
    DispatchQueue.global(qos: .background).async {
        // 백그라운드에서 데이터 로드
        let data = self.fetchDataFromServer()
        
        DispatchQueue.main.async {
            // UI 업데이트
            self.tableView.reloadData()
        }
    }
}

 

  • DispatchQueue.global에서 데이터를 가져오는 작업을 실행합니다.
  • 작업이 완료되면 Main Queue에서 UI를 업데이트하여 사용자 경험을 유지합니다.

 

4. 동기(sync)와 비동기(async)

동기 실행(sync)과 비동기 실행(async)은 작업을 추가하는 방식의 차이를 나타냅니다.

  • sync: 호출한 스레드에서 작업이 완료될 때까지 기다립니다.
  • async: 호출한 스레드에서 작업을 추가한 후 즉시 반환합니다.
let queue = DispatchQueue(label: "com.example.testQueue")

// 비동기 실행 (async)
queue.async {
    print("Async 작업 1 시작")
    sleep(2) // 작업 지연
    print("Async 작업 1 완료")
}
print("Async 호출 후 다음 코드 실행 가능")

// 동기 실행 (sync)
queue.sync {
    print("Sync 작업 2 시작")
    sleep(2)
    print("Sync 작업 2 완료")
}
print("Sync 호출 후 다음 코드 실행")
Async 호출 후 다음 코드 실행 가능
Async 작업 1 시작
Async 작업 1 완료
Sync 작업 2 시작
Sync 작업 2 완료
Sync 호출 후 다음 코드 실행

 

 

 

 

 

  • 비동기 작업은 작업 큐에 추가한 뒤 바로 반환되므로 호출 이후의 코드를 즉시 실행할 수 있습니다.
  • 동기 작업은 완료될 때까지 기다리므로 호출 이후의 코드 실행이 지연됩니다.

 

5. Dispatch Group

Dispatch Group은 여러 비동기 작업을 그룹으로 묶어 완료 시점을 추적하는 데 사용됩니다.
모든 작업이 끝난 후 후속 작업을 실행하고 싶을 때 유용합니다.

let group = DispatchGroup()

group.enter()
DispatchQueue.global().async {
    print("작업 1 시작")
    sleep(2)
    print("작업 1 완료")
    group.leave()
}

group.enter()
DispatchQueue.global().async {
    print("작업 2 시작")
    sleep(3)
    print("작업 2 완료")
    group.leave()
}

group.notify(queue: .main) {
    print("모든 작업 완료. UI 업데이트 실행")
}

 

작업 1 시작
작업 2 시작
작업 1 완료
작업 2 완료
모든 작업 완료. UI 업데이트 실행

 

  • group.enter()와 group.leave()로 작업의 시작과 완료를 명시합니다.
  • group.notify를 통해 모든 작업이 끝난 후 실행할 작업을 정의합니다.

 

직렬(Serial) 큐와 동시(Concurrent) 큐의 차이는 무엇인가요?

직렬 큐와 동시 큐는 작업 실행 방식의 차이를 나타냅니다. 두 큐는 모두 GCD에서 작업을 관리하는 데 사용되지만, 작업 처리 순서와 동시에 실행 여부에서 차이가 있습니다.

 

1. 직렬 큐 (Serial Queue)

특징

  • 작업을 하나씩 순차적으로 실행합니다.
  • 하나의 작업이 끝나야 다음 작업을 실행합니다.
  • 실행 순서를 보장합니다.
  • 작업이 동시에 실행되지 않으므로 스레드 충돌 위험이 적습니다.
let serialQueue = DispatchQueue(label: "com.example.serialQueue")

serialQueue.async {
    print("작업 1 시작")
    sleep(1)
    print("작업 1 완료")
}

serialQueue.async {
    print("작업 2 시작")
    sleep(1)
    print("작업 2 완료")
}

serialQueue.async {
    print("작업 3 시작")
    sleep(1)
    print("작업 3 완료")
}
작업 1 시작
작업 1 완료
작업 2 시작
작업 2 완료
작업 3 시작
작업 3 완료

 

  • 작업들이 순서대로 하나씩 실행됩니다. 
  • 직렬 큐는 작업이 겹치지 않으므로 실행 순서를 보장합니다. 

 

 

2. 동시 큐 (Concurrent Queue)

특징

  • 작업을 동시에 실행합니다.
  • 실행 순서는 작업이 큐에 추가된 순서와 같지만, 작업 완료 순서는 보장되지 않습니다.
  • 작업이 병렬로 실행되므로 성능이 향상될 수 있습니다.
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)

concurrentQueue.async {
    print("작업 1 시작")
    sleep(1)
    print("작업 1 완료")
}

concurrentQueue.async {
    print("작업 2 시작")
    sleep(2)
    print("작업 2 완료")
}

concurrentQueue.async {
    print("작업 3 시작")
    sleep(1)
    print("작업 3 완료")
}
작업 1 시작
작업 2 시작
작업 3 시작
작업 1 완료
작업 3 완료
작업 2 완료

 

  • 동시 큐에서는 작업이 병렬로 실행되기 때문에 작업 완료 순서는 달라질 수 있습니다.

 

3. Main Queue와 Global Queue의 연관성

  • Main Queue:
    • GCD의 직렬 큐 중 하나로, 메인 스레드에서 실행됩니다.
    • UI 작업과 같이 순차적 처리가 필요한 작업에 사용됩니다.
  • Global Queue:
    • GCD의 동시 큐 중 하나로, 시스템에서 제공하는 병렬 처리 큐입니다.
    • QoS(Quality of Service)를 통해 우선순위를 지정할 수 있습니다.

 

 

글로벌 큐(Global Queue)와 메인 큐(Main Queue)는 어떻게 다르나요?

Global QueueMain Queue는 모두 GCD에서 제공하는 디스패치 큐이지만, 목적과 실행 방식에서 차이가 있습니다.

 

1. 글로벌 큐 (Global Queue)

특징

  • **시스템이 제공하는 동시 큐 (Concurrent Queue)**입니다.
  • 여러 작업을 병렬로 실행할 수 있습니다.
  • 작업의 실행 순서는 보장되지 않으며, 완료 순서도 다를 수 있습니다.
  • **QoS (Quality of Service)**를 지정하여 작업의 중요도에 따라 우선순위를 조정할 수 있습니다.

사용 목적

  • 백그라운드 작업, 병렬 처리가 필요한 연산 등에 사용됩니다.
  • UI 업데이트와 같은 작업에는 적합하지 않습니다.
DispatchQueue.global(qos: .userInitiated).async {
    print("글로벌 큐에서 실행되는 작업")
}

 

QoS 우선순위

QoS 설명
.userInteractive UI와 관련된 즉각적인 작업 (가장 높은 우선순위)
.userInitiated 사용자가 요청한 작업 (주로 데이터 로드 등)
.utility 백그라운드 작업 (오래 걸리는 작업, 낮은 우선순위)
.background 사용자에게 보이지 않는 작업 (예: 데이터 동기화, 가장 낮은 우선순위)

 

 

2. 메인 큐 (Main Queue)

특징

  • **직렬 큐 (Serial Queue)**이며, 메인 스레드에서 실행됩니다.
  • UI 업데이트, 사용자와의 상호작용 등은 반드시 메인 큐에서 실행해야 합니다.
  • 작업은 순차적으로 실행되며, 실행 순서를 보장합니다.

사용 목적

  • UI 업데이트 작업, 사용자 입력 처리 등에 사용됩니다.
DispatchQueue.main.async {
    print("메인 큐에서 실행되는 작업")
    self.label.text = "UI 업데이트"
}

 

 

3. 두 큐의 조합

백그라운드에서 데이터를 처리한 뒤, 메인 큐에서 결과를 사용자에게 표시.

DispatchQueue.global(qos: .background).async {
    let data = self.fetchData()
    DispatchQueue.main.async {
        self.updateUI(with: data)
    }
}