GCD (DispatchQueue) 정리

iOS 프로그래밍에서 Queue는 크게 두 가지가 있음.

  1. GCD = GCD를 보통 Dispatch Queue라고 함.
  2. Operation Queue —> 여기서 이건 다루지 않음.
  • GCD를 통해 직접적으로 쓰레드를 관리하지 않고 “Queue”라는 개념을 통해 작업을 분산처리. → Queue만 만들어서 그 안에 작업을 넣기만 하면 iOS가 알아서 쓰레드 들을 생성해서 그곳으로 작업을 보냄.

  • GCD를 사용해 시스템에서 알아서 쓰레드 숫자를 관리함.

  • GCD를 사용하는 이유 : 다른 쓰레드에서 오래걸리는 작업들을 “비동기적으로 동작”하도록 만들어 줌. (몇몇 API 들은 내부적으로 다른 쓰레드에서 비동기적으로 실행되도록 설계되어 있음. Ex) URLSession)

들어가기 전에 알아두어야 할 내용

  1. 동기(Synchronous)와 비동기(asynchronous)

  2. 직렬(Serial)과 동시(Concurrent)

  3. 동기와 비동기

  1. 동기(Sync)
    – 메인 쓰레드에서 다른 쓰레드로 보낸 작업이 끝날때까지 기다림. 다른 쓰레드로 보낸 작업이 끝나기 전에는 메인쓰레드에서 그 다음 작업을 실행하지 않고 기다린다.
  2. 비동기(Async)
    – 메인 쓰레드에서 비동기 작업은 작업이 끝날때까지 기다리지 않고 바로 다음 작업을 실행한다. 즉, 작업을 다른 쓰레드로 보내고 그 작업이 끝나던 말던 신경쓰지 않고 그냥 다음 작업을 실행시킨다.
  1. 직렬과 동시
    1)직렬 (Serial)
    – Queue자체가 받아드린 작업을 한개의 쓰레드로만 보내는 큐
    2)동시 (Concurrent)
    – 여러 쓰레드를 사용해서 작업을 처리하는 큐

@wannabewize 님의 피드백을 바탕으로 쉽게 이해할 수 있도록 실행시켜봤습니다.

예시

1번 예시 : main 큐와 serial 큐의 처리. (참고 : main 큐는 기본적으로 serial임, 우리가 일반적으로 작성하는 코드가 DispatchQueue.main.sync {} 코드 내부에서 돌아감.)

let queue = DispatchQueue(label: “MyQueue”)

    queue.async {
        print("Task1 Done”)
    }
    print("Task1 queued”)

    queue.sync {
        print("Task2 Done”)
    }
    print("Task2 queued”)
    
//1차 시도
     /*
     Task1 queued
     Task1 Done
     Task2 Done
     Task2 queued
     */

//2차 시도
    /*
     Task1 queued
     Task1 Done
     Task2 Done
     Task2 queued
     */
           
//3차 시도
    /*
     Task1 Done
     Task1 queued
     Task2 Done
     Task2 queued
     */
          
 //4차 시도
    /*
     Task1 queued
     Task1 Done
     Task2 Done
     Task2 queued
     */

 //5차 시도
    /*
     Task1 Done
     Task1 queued
     Task2 Done
     Task2 queued
     */

1번 예시 설명

  • main 큐와 커스텀 큐 모두 Serial 큐.
  • 첫번째 작업인 queue.async 내부의 작업은 비동기 처리이기 때문에 우선 다음 업무를 실행시키고 업무가 완료되는 대로 print 시킨다. 다만, Serial 큐라서 하나의 쓰레드에서 관리되기 때문에 무조건 queue.sync 내부의 업무 전에 실행이 된다.
  • Main 큐에서 실행시킨 업무 들은 Serial이기 때문에 무조건 먼저 선언된 업무가 먼저 출력이 됨
  • "Task2 Done"과 "Task2 Queued"는 queue를 동기처리 했기 때문에 무조건 “Task2 Done”이 우선적으로 프린트되고 나서 "Task2 Queued"가 프린트됨.

2번 예시 : main 큐와 Concurrent큐의 처리
let queue = DispatchQueue(label: “MyQueue”, attributes: .concurrent)

        queue.async {
            print("Task1 Done”)
        }
        print("Task1 queued”)

        queue.sync {
            print("Task2 Done”)
        }
        print("Task2 queued”)

    //1차 시도
    /*
     Task1 Done
     Task1 queued
     Task2 Done
     Task2 queued
     */

    //2차 시도
    /*
     Task1 queued
     Task2 Done
     Task2 queued
     Task1 Done
     */


    //3차 시도
    /*
    Task1 Done
    Task1 queued
    Task2 Done
    Task2 queued
    */        

    //4차 시도
    /*
     Task1 Done
     Task1 queued
     Task2 Done
     Task2 queued
     */

    //5차 시도
    /*
     Task1 queued
     Task1 Done
     Task2 Done
     Task2 queued
     */

예시 2번 설명

  • 1번과 똑같이 main 큐에서 처리되는 업무(Task2)는 무조건 우선 작성된 순서대로 print됨.
  • 여기서 Serial과의 차이점을 가장 크게 보이는 것은 2차 시도와 같이 “Task1 Done"이 가장 마지막에 출력될 수 있다는 것임. 그 이유는 Concurrent 큐는 Serial 큐와 다르게 여러개의 쓰레드에 업무 들을 분산시켜서 처리한다. 그래서 “Task1 Done”이 출력되기 전에 다른 쓰레드에서 다른 업무들이 우선적으로 끝났기 때문에 마지막에 Print 된 것이다.
  • 여기서도 1번과 같이 무조건 task 2는 “Task2 Done”이 먼저 print되고나서 “Task queued”가 print됨. 다만 “Task1 Done”이 “Task2 Done”과 “Task2 queued” 사이에 완료되서 들어갈 수는 있음.

프로젝트 예제 zip 파일 : https://drive.google.com/file/d/1sI8_PVVdu4S-LNaBVYBzyouD1gtYDgmk/view?usp=sharing

*** 피드백은 언제나 환영입니다~ㅎㅎ***

좋아요 5

꽤 깊게 잘 정리하고 있네요~

task done, task queue 중 어떤 코드가 먼저 동작하는냐의 차이는 sync/async 에서 기인해요.

아래가 서로 반대되는 쌍이에요.

  • sync vs async
  • serial vs concurrent

sync vs concurrent, async vs serial 이렇게 비교하기는 어렵죠.

아래는 serial vs current 비교 예제에요.

serial queue 큐 예제

let queue = DispatchQueue(label: "myQueue1")

queue.async {
    for i in 0...10 {
        print("Task1", i)
        Thread.sleep(forTimeInterval: 1)
    }
}

queue.async {
    for i in 0...10 {
        print("Task2", i)
        Thread.sleep(forTimeInterval: 1)
    }
}

아래는 concurrent 큐 예제입니다.

let queue2 = DispatchQueue(label: "myQueue2", attributes: .concurrent)

queue2.async {
    for i in 0...10 {
        print("Task3", i)
        Thread.sleep(forTimeInterval: 1)
    }
}

queue2.async {
    for i in 0...10 {
        print("Task4", i)
        Thread.sleep(forTimeInterval: 1)
    }
}
좋아요 5