Concurrency - GCD(Grand Central Dispatch)

Marble·2024년 12월 26일

Concurrency

목록 보기
3/10

이번 게시글에서는 Swift에서 비동기 프로그래밍을 위한 접근 방식인 GCD에 대해 알아보겠습니다.

GCD란?

GCD(Grand Central Dispatch)는 Apple의 비동기 프로그래밍을 위한 기술로, 멀티코어 프로세서의 성능을 극대화하기 위해 설계되었습니다. GCD는 작업을 큐에 추가하고, 시스템이 이를 적절히 스케줄링하여 실행합니다. 이때 큐는 FIFO(First In First Out) 방식으로 작동하며, 여러 개의 큐를 생성하여 다양한 작업을 동시에 처리할 수 있습니다. GCD는 스레드 관리를 자동으로 처리해 주기 때문에 개발자는 스레드 생성이나 종료에 대한 부담을 덜 수 있습니다.

GCD를 활용한 예를 들어보겠습니다. 여러 개의 API 요청을 동시에 처리해야 할 때 GCD를 사용하여 각 요청을 동시 큐에 추가하고, 결과를 메인 스레드에서 처리하는 방식입니다. 이를 통해 앱의 응답성을 높일 수 있습니다.

GCD는 크게 두 종류의 큐로 나눌수 있는데 첫 번째는 직렬 큐(Serial Queue)로 작업이 순차적으로 실행됩니다. 두 번째는 동시 큐(Concurrent Queue)로 여러 작업이 동시에 실행될 수 있습니다. Swift에서는 GCD를 사용하기 위해 주로 DispatchQueue를 활용하여 작업을 관리하며, 동시성과 병렬성을 지원합니다.

DispatchQueue

DispatchQueue는 기본적으로 위 이미지처럼 사용할 수 있습니다. 몇 개의 옵션들에 대해 알아보겠습니다.

큐 종류

DispatchQueue를 사용할 때 크게 세 종류의 큐를 선택하여 사용할 수 있습니다.

  1. Main Queue
    메인 큐는 메인 스레드에서 작업을 처리하는 큐입니다. 메인 스레드에서 동작하기 때문에 하나만 존재하며, Serial Queue의 특성을 가지기 때문에 큐에 쌓이는 작업이 순차적으로 진행됩니다. 화면에 보여지는 앱 요소들은 모두 메인 스레드에서 그리기 때문에 UI업데이트와 관련된 모든 작업은 메인 큐에 할당되어야 합니다.

    
    let array = [1,2,3,4,5]
    
    for i in array {
    	DispatchQueue.main.async {
        	print(i)
    	}
    }
    
    // 1
    // 2
    // 3
    // 4
    // 5
  2. Global Queue
    글로벌 큐는 메인 스레드가 아닌 다른 스레드, 즉 백그라운드 스레드에서 작업을 처리합니다. Serial Queue의 특성을 지닌 메인 큐와 달리 Concurrent 특성을 띄며, 여러 스레드로 작업이 분산되어 동시에 처리됩니다. 작업이 분산되어 처리되다 보니 작업이 종료되는 순서를 보장하지 않습니다.

    
    let array = [1,2,3,4,5]
    
    for i in array {
    	DispatchQueue.global().async {
        	print(i)
    	}
    }
    
    // 순서가 랜덤으로 출력
  3. Custom Queue
    커스텀 큐는 사용자가 임의로 설정한 큐입니다. 기본적으로는 Serial의 특성을 지니지만, 생성시에 attributes 인자를 통해 concurrent로 변경할 수 있습니다.

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

QoS

QoS는 Quality of Service의 약자로 작업을 처리할 때 우선순위를 지정하기 위해서 사용합니다.

  1. userInteractive - 사용자와 직접 상호작용 하는 작업(ex. UI 관련 이벤트)
    유저와 상호작용을 하는 작업들이다보니 소요시간이 상당히 짧은 편입니다.
  2. userInitiated - 클릭할 때 작업을 수행하는 즉각적인 결과가 필요한 작업
    유저에게 즉각적인 결과를 줘야 하지만 작업이 끝난 뒤에나 의미 있을 때 사용하는 것이 효과적입니다.
  3. 기본값 - 일반적인 작업
    qos를 선택하지 않을 때 기본값으로 선택되는 qos입니다.
  4. utility - 즉각적인 결과가 필요하지 않고 길게 실행되는 작업
    데이터 다운로드 및 불러오기 등 progress bar와 함께 실행되는 작업등에서 사용합니다.
  5. background - 유저가 직접적으로 보이지 않고 백그라운드에서 처리되는 작업
    데이터 미리 가져오기, 로컬 DB에 데이터 저장하는 작업 등 우선순위가 높지 않는 작업에 사용합니다.
  6. unspecified - QoS 정보가 없음을 나타낸다

sync/async

Sync는 큐에 작업을 등록한 이후에 해당 작업이 완료될 때까지 더 이상 코드를 진행하지 않고 기다리는 것을 의미하고, Async는 큐에 작업을 등록하면 작업의 완료여부와 상관없이 계속 코드를 실행시키는 것을 의미합니다.

주의할 점

let array = [1,2,3,4,5]

for i in array {
    DispatchQueue.main.sync {
        print(Thread.current)
        print(2)
    }
}

위 코드는 메인 스레드에 sync로 작업을 요청하는 코드입니다. 이를 실행하면 error: Execution was interrupted, reason: EXC_BREAKPOINT (code=1, subcode=0x18018ceec)과 같은 에러가 발생하는데 그 이유는 Swift에서 기본적으로 코드는 메인 스레드에서 작업을 합니다. DispatchQueue를 사용해 sync로 Task를 보내면 Task가 끝날 때까지 요청을 보낸 스레드가 대기 상태로 바뀌는데, 이때 작업중이던 스레드는 메인 스레드입니다. Task를 받은 스레드도 메인 스레드지만 대기 상태이기 때문에 작업을 시작하지 못하는 교착상태가 되기 때문에 에러가 발생합니다.

Reference

[Swift] GCD(Grand Central Dispatch) - Dispatch Queue

profile
개발자가 되고 싶은 공돌이

0개의 댓글