Dispatch Queue

라무·2023년 8월 9일

정의

💡 앱의 main thread나 background thread에서 순차적 혹은 동시에 실행할 일들을 관리하는 객체
  • dispatch queue는 FIFO 데이터 구조
  • dispatch queue는 순차적 혹은 동시에 일들을 수행한다
  • dispatch queue에 있는 작업들은 시스템에서 관리하는 thread pool에서 실행된다!!!
  • 하지만 시스템은 main thread를 나타내는 dispatch queue들의 실행만을 보장한다(즉, main thread에서의 실행만을 보장함)
  • dispatch queues를 사용하면 호출자(caller)와 관련하여 비동기/ 동기식으로 임의의 코드 블럭을 수행할 수 있다
  • dispatch queues은 앱에서 task를 비동기적으로 동시에 수행 할 수 있는 손쉬운 방법
    • task → 앱이 수행해야하는 작업

cf) Queue의 종류

Serial

  • 하나의 작업이 끝나야지만 다음 작업을 시작하는 큐
  • 즉, 한번에 하나의 작업만 하는 큐

Concurrent

  • 동시에 여러 작업들을 시작하는 것
  • 그럼 아무거나 먼저 시작? → nono, 먼저 들어온 작업이 먼저 시작됨

dispatch Queue의 타입

Serial

  • Serial Queue(Private dispatch queue)라고 불린다
  • 큐에 추가된 순서대로 한번에 하나의 task를 실행한다 → 즉, 하나의 작업이 끝나야만 다른 하나의 작업이 시작된다
  • 현재 실행중인 task는 dispatch queue에서 관리하는 고유한 쓰레드에서 실행된다
    • serial은 하나의 thread로만 보내진다
  • Serial queue는 종종 특정 자원에 대한 액세스를 동기화하는데 사용된다
  • 필요한 만큼 Serial Queue를 작성할 수 있고 각 큐는 다른 모든 큐와 관련하여 동시에 작업한다 → 즉, Serial queues를 4개 작성하면 각 큐는 하나의 작업(taks)만 실행하지만, 최대 4개의 task각 각 큐에서 실행

Concurrent

  • Concurrent queue(global Dispatch queue)라고 불린다
  • 동시에 하나 이상의 task를 실행하지만 task는 큐에 추가된 순서대로 계속 시작된다 → 앞의 task들이 끝났는지의 여부에 상관없이 들어온 task를 실행
    • concurrent는 여러 thread로 나뉘어진다
  • 현재 실행중인 task는 dispatch queue에서 관리하는 고유한 쓰레드에서 실행된다
  • 특정 시점에서 실행되는 정확한 task의 수는 가변적이며 시스템 조건에 따른다

GCD란?

💡 ios에서 멀티코어 프로세서에 코드를 동시에 실행시키게 해주는 프레임워크
  • GCD에서는 하나의 작업단위인 Task를 Dispatch queue에 전달하기만 하면 나머지는 시스템이 알아서 처리하고 실행시켜 준다
    • Task의 단위는 블록이나 DispatchWorkItem의 인스턴스 형태로 존재한다
  • GCD는 시스템 레벨에서 구동된다
  • GCD는 적절하고 균형있게 사용 가능한 시스템 자원을 어플리케이션에 분배하기 때문 →즉, GCD에 Task만 잘 전달하면 시스템이 알아서 thread를 배정해서 Task를 실행시켜준다!!
  • GCD에서 Queue는 FIFO형태이고 GCD는 Queue에 전달된 Task에 Thread를 Queue의 특성에 맞게 배정한다
  • DispatchQueue클래스에서 Task를 전달시킬 Queue는 기본적으로 두 종류
    • 순차적으로 task를 실행시키는 queue(아마 Main dispatch queue)
    • 동시에 task를 실행시키는 queue(아마 global dispatch queue)

Cocoa Application에서 제공하는 Queue

즉, 미리 만들어진 queue들

Main dispatch Queue

  • 앱의 main thread에서 task를 실행하는, 전역적으로 사용가능한 serial Queue
    • Main queue에 전달된 Task는 항상 Main thread에서 실행된다
  • Serial queue이므로 하나의 작업(task)가 끝나야 다음 작업을 실행한다
    • 모든 UI작업은 이 queue에서 실행된다
  • main dispatch queue는 앱의 실행루프와 함께 작동하여 큐에 있는 task의 실행을 실행루프에 연결된 다른 이벤트 소스의 실행과 얽힌다

Main dispatch queue 사용코드

  • main은 타입 프로퍼티이다 → 즉, 타입 자체에 속한 프로퍼티
DispatchQueue.main.sync {
	//code
}

DispatchQueue.main.async {
	//code
}

Global dispatch Queue

  • Concurrent Queue이다
  • 동시에 하나 이상의 task를 실행하지만 task는 큐에 추가된 순서대로 계속 시작됨
  • 실행 중인 task는 dispatch queue에서 관리하는 고유한 쓰레드에서 실행된다

Global dispatch queue 사용코드

  • global은 함수로 qos를 파라미터로 받는다
DispatchQueue.global().async {
	//code
}

cf) qos란?

  • quality of service로 중요도를 의미한다
  • qos는 dispatchQueue에서 수행할 작업을 분류한다
  • DispatchQos에 qosClass라는게 있게 있다
    • DispatchQos.qosClass는 작업 실행을 위한 우선순위를 지정하는 클래스이다
    • DispatchQos.QoSClass는 enum으로 구성되어있다
    • qos 파라미터로 들어간건 DispatchQos.QoSClass이다(즉, enum의 case가 들어가진다)
  • 작동시킬 QoS를 지정함으로써 중요도를 표시하고 시스템이 우선순위를 정하고 이에따라 스케쥴링함

Sync와 Async

Sync

  • 하나의 작업에 대한 응답이 올때까지 기다린다
  • 즉, 하나의 작업이 끝날때까지 기다린다 → 단일 작업에 관한 것

global()일때

  • sync니까 global큐가 끝날때까지 다른 작업을 하지 않는다
func example() {
    DispatchQueue.global().sync {
        for i in 1...5 {
            print(i)
        }

        print("==================")
    }

    for i in 100...105 {
        print(i)
    }
}

example()
// 결과값
/*
1
2
3
4
5
==================
100
101
102
103
104
105
*/

Serial일때

func example2() {
    let ramooQueue = DispatchQueue(label: "ramoo")
    
    ramooQueue.sync {
        for i in 1...5 {
            print(i)
        }
    }
    print("========================")
    for i in 100...105 {
        print(i)
    }
}
// 결과값
/*
1
2
3
4
5
==================
100
101
102
103
104
105
*/

main.sync

메인 큐에 이미 수행 중인 블락(코드)가 있는 상태에서 DispatchQueue.main.sync{}을 호출하여 Task를 전달하면, Deadlock이 발생한다!!

→ 당연함,,, 반드시 한가지 종류의 일만 할 수 있는 작업자인데 이 작업자가 이미 A에 관한 일하고 있는 상태에서 갑자기 이거 하지말고 B 일해! 하면 과부화걸림,,

Async

  • 일단 큐에 작업을 추가하고 작업이 끝나기를 기다리지 않는다 + 다른 작업을 할수도 있다
  • 요청에 대한 응답을 기다리지 않는다

global()

  • 결과는 매번 실행할 때마다 다르다!!
    • 이유: async는 앞의 작업의 완료여부와 상관없이 다음 작업에 관련된 코드를 실행

      → 즉, async는 앞의 작업을 기다리지 않는다

  • global은 앞의 작업의 완료의 유무에 상관없이 동시에 실행할 수 있으므로 ⭐️가 끝나지 않아도 🔥을 실행할 수 있다
func example3() {
    DispatchQueue.global().async {
        for i in 1...5 {
            print("\(i)⭐️")
        }

        print("==================")
    }

    DispatchQueue.global().async {
        for i in 200...205 {
            print("\(i)🔥")
        }

        print("==================")
    }

    for i in 100...105 {
        print("\(i)🌸")
    }
}
//결과 코드
/*
1⭐️
200🔥
100🌸
201🔥
2⭐️
101🌸
202🔥
102🌸
203🔥
204🔥
103🌸
3⭐️
4⭐️
5⭐️
==================
205🔥
==================
104🌸
105🌸
*/

serial

  • 결과는 매번 실행할 때마다 다르다!!
    • 이유: async는 앞의 작업의 완료여부와 상관없이 다음 작업에 관련된 코드를 실행

      → 즉, async는 앞의 작업을 기다리지 않는다

  • async이기 때문에 앞의 작업을 기다리지 않고 serial queue에 넣을 수 있다 하지만 serial queue는 순차적으로 실행되기 때문에 ⭐️가 끝난 다음에 🔥가 실행된다
func example4() {
    let ramooQueue = DispatchQueue(label: "ramoo")
    
    ramooQueue.async {
        for i in 1...5 {
            print("\(i)⭐️")
        }

        print("==================")
    }
    
    ramooQueue.async {
        for i in 200...205 {
            print("\(i)🔥")
        }

        print("==================")
    }
    
    for i in 100...105 {
        print("\(i)🌸")
    }
}

async&sync 그리고 concurrent&serial(총 정리!!)

async&sync → 작업을 보내는 시점에서 기다릴지 말지에 대해 다룬다

  • async → 작업을 보내고 응답 안기다려
  • sync → 작업을 보내고 응답 기다려

concurrent&serial → queue로 보내진 작업들을 여러개의 thread로 보낼 것인지인지 하나의 thread로 보낼 것인지

  • concurrent → 여러 스레드로 보내
  • serial → 하나의 스레드로 보내
  1. concurrent(global queue)와 async
    • 메인 스레드의 작업 흐름이 태스크를 queue에서 넘기자마자 반환된다(async) → 즉, task의 작업이 끝났는지와 상관없이 queue가 task를 메인(혹은 기타) 스레드에 계속 넘긴다
    • queue가 task를 넘길 때 하나의 스레드에만 넘기는 것이 아니라 다른 스레드에도 넘길 수 있고 여러 스레드들이 동시에 실행(concurrent)
  2. serial(main queue)와 async
    • 메인 스레드의 작업 흐름이 태스크를 queue에서 넘기자마자 반환된다(async) → 즉, task의 작업이 끝났는지와 상관없이 queue가 task를 메인 스레드에 계속 넘긴다
    • queue가 task를 메인스레드에 계속 넘겨도 메인스레드는 들어온 작업을 순차적으로 처리한다(serial)
  3. concurrent와 sync
    • 메인 스레드의 작업 흐름이 queue에서 넘긴 task가 끝날때까지 멈춰있다(sync) → 즉, 메인스레드가 하나의 작업만 한다
    • 현재 메인 스레드의 task말고 다른 스레드에서도 작업이 동시에 실행될 수 있으므로 큐에 있는 task들은 다른 스레드로 넘어간다(concurrent)
  4. serial(Main queue)와 sync
    • 메인 스레드의 작업 흐름이 queue에서 넘긴 task가 끝날때까지 멈춰있다(sync) → 즉, 메인스레드가 하나의 작업만 한다
    • queue에 있는 다른 task들은 메인 스레드에 있는 task가 다 끝나야지만 메인스레드로 넘어가서 실행될 수 있다(serial)

참고한 블로그

[iOS] 차근차근 시작하는 GCD — 4

[iOS] GCD (Grand Central Dispatch) 란? (feat. main & global dispatch queue)

profile
ios 개발을 하고있는 라무의 사적인 기술 블로그

0개의 댓글