GCD

Martin·2023년 2월 18일

TIL 아카이브

목록 보기
8/11

GCD 기본

DispatchQueue 1: Serial / Concurrent

class DispatchQueue: DispatchObject
  • Dispatch : 보내다(파견하다)
  • Queue : 대기열

DispatchQueue : 대기열에 보낸다는 뜻

  • GCD를 사용하기 위한 대기열
  • 작업을 추가해주기만 하면 시스템은 알아서 스레드를 관리하여 작업을 처리
  • FIFO(First In, First Out)
  • dispatchQueue에 작업을 넘길 때 꼭 필요한 것 두 가지
    • 단일 스레드 사용 / 다중 스레드 사용 : Serial, Concurrent
    • 동기로 처리 / 비동기로 처리 : sync, async

Serial / Concurrent

  • Serial은 단일 스레드에서만 작업을 처리하고, Concurrent는 다중 스레드에서 작업을 처리
  • dispatchQueue 초기화 시,attributes를 따로 .concurrent로 설정하지 않으면 그 기본 값은 Serial

main과 global()

// Serial Queue
DispatchQueue(label: "Serial")
DispatchQueue.main
// main은 전역적으로 사용되는 Serial DispatchQueue 입니다.

// Concurrent Queue
DispatchQueue(label: "Concurrent", attributes: .concurrent)
DispatchQueue.global()

image alt


  • DispatchQueue.main : Serial (단일 스레드), main 스레드에서 작업을 처리
  • DispatchQueue.global() : Concurrent(다중 스레드), 새로운 스레드를 만들어 그 위에서 작업을 처리, 메모리에 올라왔다가 작업이 끝나고 나면 메모리에서 제거
  • Serial Queue에서 작업할 건지, Concurrent Queue에서 작업할 것인지를 정해주었다면 다음으로는 동기/비동기 처리를 정해주면 된다.
// 동기, sync
DispatchQueue.main.sync {}
DispatchQueue.global().sync {}

// 비동기, async
DispatchQueue.main.async {}
DispatchQueue.global().async {}

Main Thread

  • 앱의 기본이 되는 스레드
  • ARC와 같은 생명주기를 가짐
  • 앱이 실행되는 동안, 계속 메모리에 올라와 있는 기본 스레드
  • 동시성 프로그래밍은 메인 스레드에서부터 필요한 만큼의 스레드를 파생 시키는 것
  • 전역적으로 사용 가능
    image
  • global 스레드들과는 다르게 Run Loop가 자동으로 설정되고 실행
    • 메인 스레드에서 동작하는 Run Loop를 Main Run Loop라고 함
  • UI 작업은 메인 스레드에서만 작업할 수 있다.

DispatchQueue 2: sync / async

  • DispatchQueue.global().sync {} : main 스레드 말고 새로운 스레드를 만들어서 작업을 처리할거야. 그런데 내 작업이 끝날 때까지 기다려
  • DispatchQueue.global().async {} : main 스레드 말고 새로운 스레드를 만들어서 작업을 처리할건데, 다음 작업이 있다면 날 기다리지 말고 처리해도 좋아. 나는 새 스레드에서 알아서 작업할게

DispatchQueue 3: 그 외 다양한 기능들

DispatchWorkItem

  • DispatchWorkItem : DispatchQueue의 캡슐화, execute 파라미터로 전달
import Foundation

let red = DispatchWorkItem {
    for _ in 1...5 {
        print("🥵🥵🥵🥵🥵")
        sleep(1)
    }
}

let yellow = DispatchWorkItem {
    for _ in 1...5 {
        print("😀😀😀😀😀")
        sleep(1)
    }
}

let blue = DispatchWorkItem {
    for _ in 1...5 {
        print("🥶🥶🥶🥶🥶")
        sleep(2)
    }
}

DispatchQueue.main.async(execute: yellow)
DispatchQueue.global().sync(excute: blue)

DispatchQueue.global().async(execute: yellow)
DispatchQueue.global().sync(execute: blue)
DispatchQueue.main.async(execute: red)

yellow는 상관 없이 무조건 실행, red는 blue가 끝난 후 실행되기 시작

DispatchQueue.global().sync(execute: yellow)
DispatchQueue.global().async(execute: blue)
DispatchQueue.main.async(execute: red)

yellow 실행이 모두 된 후, blue와 red가 실행

DispatchQueue.main.async(execute: yellow)
DispatchQueue.global().async(execute: blue)
DispatchQueue.global().sync(execute: red)

yellow를 실행하려했지만, sync가 존재하므로 red를 기다린 후 실행, red가 실행되기 전에 blue가 비동기로 실행

DispatchQueue.main.async(execute: yellow)
DispatchQueue.global().sync(execute: blue)
DispatchQueue.global().async(execute: red)

yellow를 실행하려했지만, sync가 존재하므로 BLUE를 기다린 후 실행 후 red과 실행됨과 동시에 yellow가 실행

asyncAfter

  • async 메서드를 원하는 시간에 호출해줄 수 있는 메서드
DispatchQueue.global().asyncAfter(deadline: .now() + 5, execute: yellow)
                                  
DispatchQueue.global().asyncAfter(wallDeadline: .now() + 5, excute: blue)

-.now 로부터 5초 후 yelow DispatchWorkItem 실행

wallDeadLine : 시스템(기기)의 시간을 기준

asyncAndWait

  • 메서드를 사용하면 비동기 작업이 끝나는 시점을 기다림
DispatchQueue.global().asyncAndWait(execute: yellow)
print("Finished!")

헷갈리는 개념 다시 잡기

sync와 async의 차이는 무엇인가요?

  • sync = 동기, async = 비동기

async와 concurrent는 구분되는 개념이라고 했습니다. 각각을 설명해보세요.

  • async = 동기, concurrent = 동시

DispatchQueue에서 serial 큐와 main 큐는 같은 것인가요?

  • main 큐는 serial 큐로 구현되어 있고, serial 큐를 커스텀으로 만들 수 있다.

DispatchQueue에서 main과 global()의 차이는 무엇인가요?

  • main은 한 스레드에서 모든 일을 처리하겠다. global()은 필요한 만큼 스레드를 생성해서 일을 처리하겠다.

main 스레드는 어떤 특징을 가지고 있나요?

  • serial queue이고 async(비동기)로 작동한다.

Serial에서 async는 어떻게 동작하나요?

  • global().sync 와 비슷한 형태로 작동?

serial 큐에서 sync로 작업을 처리하면 어떻게 될까요?

  • 순서대로 작업 처리

main.sync를 사용하면 어떻게 되나요? 그 이유는 무엇인가요?

  • main queue 에 넣은 task 가 완료되기 전까지 메인 쓰레드는 blocking waiting 상태가 되고, 작업이 이미 block 상태에 빠지기 때문에 무한 대기 상태가 일어나는데 이것을 deadlock이라고 한다.
    Swift) Main.sync외 않됌?

병렬 프로그래밍과 동시성 프로그래밍은 각각 무엇인가요? 서로 반대되는 개념일까요?

  • 동시성 프로그래밍 : 싱글코어 환경에서 멀티태스킹을 위해 시간을 분할해서 여러 프로세스와 스레드를 번갈아가며 실행
  • 병렬 프로그래밍 : 물리적으로 정확히 동시에 실행이 되는 것을 말한다. 멀티 코어에서 멀티 스레드를 실행시키는 방식

GCD 심화

DispatchQueue의 초기화

  • DispatchQueue도 커스텀하여 사용할 수 있다
convenience init(label: String,
                 qos: DispatchQoS = .unspecified,
                 attributes: DispatchQueue.Attributes = [],
                 autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit,
                 target: DispatchQueue? = nil)
label

DispatchQueue의 label을 설정해주는 파라미터, 디버깅 환경에서 추적하기 위해서 작성하는 String 값

qos

DispatchQoS 타입의 값을 받는 파라미터, Quality of Service의 약자, 실행 될 Task들의 우선 순위를 정해주는 값

attributes

DispatchQueue의 속성을 정해주는 값

  • .concurrent로 초기화한다면 다중 스레드 환경에서 코드를 처리하는 DispatchQueue가 되는 것, global() 과 비슷해짐
  • 이 값을 빈 배열, 즉 기본 값으로 아무 설정을 하지 않는다면 우리는 Serial DispatchQueue로 만들어짐
  • .initiallyInactive : active()를 호출하기 전 까지는 작업을 처리하지 않는 것
  • .initiallyInactive 예시 코드
import Foundation

let yellow = DispatchWorkItem {
    for _ in 1...5 {
        print("😀😀😀😀😀")
        sleep(1)
    }
}

let myDispatch = DispatchQueue(label: "Odong", attributes: .initiallyInactive)

myDispatch.async(execute: yellow) // 코드 블록 호출 안됨.
myDispatch.activate()
autoreleaseFrequency

DispatchQueue가 자동으로 객체를 해제하는 빈도의 값을 결정하는 파라미터

  • 객체를 autorealease해주는 빈도이며 기본값은 inherit
  • inherit : target과 같은 빈도
  • workItem: workItem이 실행될 때마다 객체들을 해제
  • never : autorelease를 하지 않음
target : 코드 블록을 실행할 큐를 target으로 설정

DispatchGroup

비동기적으로 처리되는 작업들을 그룹으로 묶어, 그룹 단위로 작업 상태를 추적할 수 있는 기능

  • async들을 묶어서 그룹의 작업이 끝나는 시점을 추적하여 어떠한 동작을 수행 가능
  • 묶어줄 async 작업들이 꼭 같은 큐, 스레드에 있지 않더라도 묶어줄 수 있음
  • async에서만 사용할 수 있으며, 동기로 처리되는 작업들은 끝나는 시점을 예측할 수 있으므로 작업 종료 시점을 따로 추적할 필요가 없음
group에 등록하기: enter, leave
  • DispatchGroup은 특별한 초기화 구문이 없음

  • DispatchGroup을 사용하는 방법

    • async를 호출하면서 파라미터로 group을 지정
    • enter, leave를 코드의 앞뒤로 호출하여 group을 지정

      enter와 leave는 DispatchGroup이 enter()부터 leave()까지 포함된다라는 의미

  • DispatchGroup 예시 코드

let group = DispatchGroup()

// enter, leave를 사용하지 않는 경우
DispatchQueue.main.async(group: group) {}
DispatchQueue.global().async(group: group) {}

// enter, leave를 사용하는 경우
group.enter()
DispatchQueue.main.async {}
DispatchQueue.global().async {}
group.leave() 
  • 묶어낸 그룹에 대해 notify() 혹은 wait()으로 작업을 추적 가능

notify

notify는 DispatchGroup의 업무 처리가 끝나는 시점에 원하는 동작을 수행하기 위한 메서드

import Foundation

let red = DispatchWorkItem {
    for _ in 1...5 {
        print("🥵🥵🥵🥵🥵")
        sleep(1)
    }
}

let yellow = DispatchWorkItem {
    for _ in 1...5 {
        print("😀😀😀😀😀")
        sleep(1)
    }
}

let blue = DispatchWorkItem {
    for _ in 1...5 {
        print("🥶🥶🥶🥶🥶")
        sleep(2)
    }
}

let group = DispatchGroup()

DispatchQueue.global().async(group: group, execute: blue)
DispatchQueue.global().async(group: group, execute: red)

// group.enter()
// DispatchQueue.global().async(execute: blue)
// DispatchQueue.global().async(execute: red)
// group.leave()

group.notify(queue: .main) {
    print("모든 작업이 끝났습니다.")
}
  • notify 메서드에 의해 group의 모든 작업이 끝나기를 기다렸다가 코드 블록을 실행

    notify의 파라미터 queue는 코드블록을 실행시킬 queue

wait

DispatchGroup의 수행이 끝나기를 기다리기만 하는 메서드

  • notify와 달리 별도의 코드 블록을 실행하지 않기에, 코드 블록을 실행시킬 queue를 지정할 필요 없음
let group = DispatchGroup()

DispatchQueue.global().async(group: group, execute: blue)
DispatchQueue.global().async(group: group, execute: red)

group.wait()
print("모든 작업이 끝났습니다.")

// group.wait(timeout: 10)
// print("모든 작업이 끝났습니다.")
  • wait 메서드에는 timeout 파라미터를 설정해줄 수 있음, group.wait(timeout: 초 단위)
profile
제로부터 시작하는 이세계 Swift

0개의 댓글