티스토리 뷰

오늘은 GCD에 대해 정리해보려 합니다! 

GCD는 iOS앱 프로그래밍에서 매우 중요한 부분이기도 하고 잘 정리된 글들이 많기 때문에, 저는 핵심적인 개념과 코드(실험?) 위주로 정리해볼까 합니다. 

GCD란? - Grand Central Dispatch의 약자로, Apple에서 멀티 코어 프로세서 및 기타 대칭적 멀티 프로세싱 시스템이 있는 시스템에 대한 애플리케이션 지원을 최적화하기 위해 개발 한 기술입니다. GCD는 스레드 풀 패턴을 기반으로 한 작업 병렬 처리의 구현으로 스레드 풀의 관리를 프로그래머가 아닌 운영체제에서 관리하기 때문에 프로그래머는 쉽게 사용할 수 있습니다. (사용하기 단순하고 성능도 좋다는 뜻) (위키 백과, 부스트코스 참고)

 

Dispatch Queue

An object that manages the execution of tasks serially or concurrently on your app's main thread or on a background thread. (애플 문서 중..)

Dispatch Queue는 앱의 기본 스레드 또는 백그라운드 스레드에서 작업 실행을 직렬(serially) 또는 동시(concurrently)에 관리하는 객체입니다. Dispatch Queue는 애플리케이션이 블록 객체 형태로 작업을 제출할 수 있는 *FIFO 큐입니다. Dispatch Queue는 작업을 직렬 또는 동시에 실행합니다. Dispatch Queue에 제출된 작업은 시스템에서 관리하는 스레드 풀에서 실행됩니다. 

* 여기서 FIFO란 선입선출 구조 즉 먼저 들어온 테스크를 먼저 처리한다는 의미입니다 :)

Dispatch Queue는 작업을 직렬로도 관리할 수도 있고 동시에 관리할 수도 있다고 했죠? 무슨 의미인지 좀 더 정확히 알아봅시다! 

Dispatch Queue는 Serial Queue와 Concurrent Queue를 제공합니다. 

Serial Queue: 한 번에 하나의 작업(task)만을 실행, (즉 처리중인 작업이 완료되기 전까지는 새로운 작업 처리를 시작하지 않음) 우리가 자주 사용하는 main queue가 대표적인 Serial Queue입니다. 

Concurrent Queue: 동시에 여러 작업(task)을 실행 (큐에 추가된 순서대로 실행되지만, 앞의 작업이 완료되기를 기다리지 않기 때문에 여러 작업 실행이 가능합니다.) global queue는 Concurrent Queue입니다. 

이제 각각의 큐가 어떤식으로 동작하는지 테스트를 해봅시다 ㅎ

먼저 기존 API에서 제공해주는 main queueglobal queue를 사용해서 테스트를 해볼 수도 있지만, 이번 테스트에서는 커스텀 큐를 만들어서 테스트할 겁니다!  (원하신다면 그냥 main queue, global queue로 진행하셔도 무방합니다.)

DispatchQueue.main.async {
    // 메인 큐는 Serial Queue ~ 
}

DispatchQueue.global().async {
    // 글로벌 큐는 Concurrent Queue ~
}

 

아아아 잠깐! 하나 더 짚고 넘어가자면, 커스텀 큐는 언제 만들고 언제 사용할까요? 커스텀 큐 역시 Serial 큐, Concurrent 큐 모두 만들 수 있습니다. 그럼 "그냥 기존 Serial 큐, Concurrent 큐인 main 큐와 global 큐 사용하면 되는 거 아니야?" 할 수 있지만 우선 두 큐는 앱 전역에서 사용되는 큐이고, main 큐 같은 경우 앱의 실행 루프와 함께 main thread에서 작동합니다. 우리가 보통 UI 관련된 task는 항상 main큐에 실행하잖아요? (안 그럼 런타임 에러 ㅎ) 아무튼 그렇기 때문에 main queue에서 처리하기는 부적합한, 직렬적으로 처리할 작업을 새로운 시리얼 큐를 만들어서 처리해 준다고 생각하면 좋을 것 같습니다. (혹시 틀린 부분이 있다면 꼭 댓글로 알려주시기 바랍니다!) 

커스텀 큐는 다음과 같이 만들 수 있습니다. 

let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)

 

DispatchQueue를 생성할 때는 원래 다음과 같이 다양할 파라미터를 입력받지만, default값들이 이미 있기 때문에, 여러 파라미터를 생략할 수 있습니다. 즉 이 말은 별도로 위 처럼 attributes를 .concurrent로 설정해주지 않으면 serial queue가 된다는 의미 입니다.

DispatchQueue(label: String, qos: DispatchQoS, attributes: DispatchQueue.Attributes, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency, target: DispatchQueue?)

 

추가적으로 각각의 큐는 sync(동기), async(비동기)로 작업을 처리할 수 있는데, sync는 큐에 작업을 추가한 후 그 작업이 완료될 때까지 기다린다고 생각하면 쉽고, async는 큐에 작업을 추가 후 그 작업이 완료될 때까지 기다리지 않는다고 생각하면 이해하기 쉽습니다.

serialQueue.async {
    for _ in (1...5) {
        print("1")
    }
}

serialQueue.async {
    for _ in (1...5) {
        print("2")
    }
}

serialQueue.async {
    for _ in (1...5) {
        print("3")
    }
}

Serial 큐는 하나의 테스크가 완전히 끝나야 다음 테스크를 진행하기 때문에 다음과 같이 순차적으로 11111, 22222, 33333 처럼 숫자가 프린트됩니다.

하지만, Concurrent 큐는 동시에 여러 작업을 처리하기 때문에, 순서가 뒤죽박죽 찍히게 됩니다. (매번 실행할 때마다 결과가 다를 수 있습니다.) 

concurrentQueue.async {
    for _ in (1...5) {
        print("1")
    }
}

concurrentQueue.async {
    for _ in (1...5) {
        print("2")
    }
}

concurrentQueue.async {
    for _ in (1...5) {
        print("3")
    }
}

 

Serial 큐야 테스크를 하나씩 순차적으로 처리하니 sync, async 결과가 같을 거고 그럼 Concurrent 큐를 sync로 처리하면 어떻게 될까요? 

concurrentQueue.sync {
    for _ in (1...5) {
        print("1")
    }
}

concurrentQueue.sync {
    for _ in (1...5) {
        print("2")
    }
}

concurrentQueue.sync {
    for _ in (1...5) {
        print("3")
    }
}

두두두두두둥

예상한 결과인가요? Concurrent 큐여도 sync(동기)로 작업을 실행하면 하나의 작업이 완료될 때까지 기다려야 하기 때문에, 순서대로 출력되는 것을 볼 수 있습니다.

Serial큐를 두 개 만들어서 async로 실행하면 두 개의 큐가 별도로 실행되기 때문에 serialQueue2가 맨 마지막에 실행됨에도 번갈아가며 문자가 출력되는 것을 확인할 수 있습니다. (하지만 serialQueue 안의 task들은 여전히 순차적으로 처리되는걸 꼭 확인하세요!)

let serialQueue = DispatchQueue(label: "serial")
let serialQueue2 = DispatchQueue(label: "serial2")

serialQueue.async {
    for _ in (1...5) {
        print("1")
    }
}

serialQueue.async {
    for _ in (1...5) {
        print("2")
    }
}

serialQueue.async {
    for _ in (1...5) {
        print("3")
    }
}

serialQueue2.async {
    for _ in (1...5) {
        print("🙂")
    }
}

 

 

다음과 같이 실행하면 결과가 어떻게 될까요? 

concurrentQueue.async {
    for _ in (1...5) {
        print("1")
    }
}

concurrentQueue.async {
    for _ in (1...5) {
        print("2")
    }
}

concurrentQueue.async {
    for _ in (1...5) {
        print("3")
    }
}

for _ in (1...5) {
    print("🙂")
}

 

정답: 순서를 알 수 없다. (실행 때마다 다름)

 

다음 포스팅에서는 DispatchWorkItem과 DispatchGroup에 대해서 정리해보겠습니다.

읽어주셔서 감사합니다 :) 

댓글
링크
최근에 올라온 글
최근에 달린 댓글