맛동산이

Swift) GCD(Grand Central Dispatch) 에 대한 이해 (feat. DispatchQueue, Runloop 본문

앱/Swift

Swift) GCD(Grand Central Dispatch) 에 대한 이해 (feat. DispatchQueue, Runloop

진ddang 2023. 7. 4. 13:34

GCD란, iOS에서 멀티코어 프로세스에서 멀티스레딩을 지원하게 해주는 애플이 개발한 기술이다.

  • GCD를 사용하기 위해서는 Dispatch라는 프레임워크를 사용하면 된다.
  • Dispatch 프레임워크에는 DispatchQueue, DispatchWorkItem과같은 다양한 클래스가 존재한다.
  • 이를 통해서 Dispatch Queue 에 작업을 보내면 그에 따라 스레드를 적절히 생성해서 실행하고 작업이 종료되면 스레드를 GCD가 자동으로 제거한다.

Serial, Concurrent

serial은 단일 스레드 환경

concurrent는 다중스레드 환경

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

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

이렇게 attributes로 줄수있으며, 기본값은 serial이다.

main, global()

main

Dispatchqueue.main과 dispatchqueue.global()이 존재한다.

.main은 기본적으로 프로그램이 실행되면, 생성되는 Default 스레드이며, 앱이 실행됨과 동시에 생성되고 종료까지 쭉 존재하는 스레드이다.

  • 전역적으로 사용이 가능하다.
  • global 스레드들과는 다르게 Run Loop가 자동으로 설정되고 실행된다. 메인 스레드에서 동작하는 Run Loop를 Main Run Loop라고 한다.

global()

global은 main과 다르게 메소드로 되어있는데, 이는 해당 메소드가 불리면 새로운 스레드를 생성하기 때문이다.

global()은 불릴때마다 새로운 스레드를 생성한다.

  • 해당 새로이 만들어진 스레드는 task를 처리하고 나면 메모리에서 제거된다.

main에서 sync를 하면 안되는 이유

main에서 sync를하면 안되는 이유는 간단하다.

deadlock에 빠지기 때문이다.

main스레드는, dispatchqueue.main.sync를 처리해주기 위해서 잠깐 멈추게 되는데, 이때 main.sync또한 멈추게 되는것이다.

즉 서로가 서로를 멈추게 하고 그대로 계속해서 멈추게되는 데드락에 빠져버린다는 사실.

하지만 global에서 main.sync하면 정상적으로 작동한다.

DispatchWorkItem

보통 dispatchqueue.main.async {} 이렇게 많이들 쓰게 되는데 이는 후행클로저의 형태이다.

DispatchQueue.main.async(execute: <#T##DispatchWorkItem#>)이기 때문이다.

따라서 이를 그냥 캡슐화를 해줄수 있다.

이를 통해 좀더 타입명시와 직관적인 코드를 짤수 있다는 장점이 있다.

let red = DispatchWorkItem {
    print("hi")
}

DispatchQueue.main.async(execute: red)

asyncAndWait

asyncAndWait 메서드를 사용하면 비동기 작업이 끝나는 시점을 기다릴 수 있다. 비동기로 처리되는 어떤 동작이 끝나기를 의도적으로 기다려야할 때 사용할 수 있다.

Race Condition 과 Thread safe

raceCondition이란, 한 값에 여러 스레드가 동시에 접근함을 뜻한다.

Thread safe하다는것은 이러한 race condition이 발생하지 않는다는뜻이다.

Main에서 UI를 업데이트 해줘야만 하는 이유

  • Thread safe하지 않기 때문이다.

UI작업을 꼭 메인 스레드에서 작업해야하는 이유는 메인 스레드에는 main RunLoop가 동작하고 있기 때문이다.

메인 스레드에서는 RunLoop가 일정한 주기를 유지하며 계속 동작하고 있습니다.

이 주기에 맞추어서 사용자의 입력을 받아서 UI를 그리게 된다.

이러한 주기를 View Drawing Cycle이라고 한다.

모든 스레드의 RunLoop에 따라 각자가 UI를 그리게 된다면 UI가 그려지는 시점이 모두 제각각이 되면, 그렇게 되면 비효율적일 뿐더러 위에서 언급한대로 Race Condition이 발생하게 된다.

즉 어느 스레드에서 언제 업데이트 할지도 모르고 동일한 text값이나 뷰에 대해서 서로 동시에 접근하게 되면 어떻게 변화시켜야하는지에 대해서 race condition이 발생하기 때문이다.

따라서 main에서만 업데이트 해주어야한다.

RunLoop

런루프는 두개의 일을 한다.

  • 특정 이벤트가 왔을 때 쓰레드가 일해야 할 때는 일하고, 일이 없으면 쉬도록하기 위해 애플에서 만든 쓰레드관리 Loop
  • 이벤트가 들어오면 이벤트 헨들러를 실행하는데, 이벤트 핸들러를 언제 실행하지 결정해주는 루프를 의미

Run Loop는 두 가지의 소스를 수신

  1. input source: 한 루프 한바퀴를 도는 동안, 다른 스레드나 다른 응용프로그램의 비동기 이벤트가 수신된 것을 확인하고, 이벤트 핸들러 수행
  1. timer source: 한 루프 한바퀴를 도는 동안, 예정된 시간이나 반복되는 간격으로 발생하는 동기 이벤트를 수신된 것을 확인하고, 이벤트 핸들러 수행

런루프는 직접 실행해야한다.

스레드를 생성할때 자동으로 생성되지 않기 때문에, Thread.current를 하게되면 런루프가 존재하면 가져오고, 없음 생성하게된다.

하지만 위에서 설명했듯, 런루프는 한번의 실행동안, 이벤트 input과 timer를 확인하고 해당 이벤트를 수행하게 된다.

이를 프로그래머가 명시적으로 실행해줘야한다.

  • 즉 런루프란, 이벤트를 받고 이를 언제 실행시킬지 정해주는 루프를 의미한다.
  • Main Thread는 애플리케이션이 실행될 때 프레임워크 차원에서 자동으로 RunLoop를 설정하고 실행

Uploaded by N2T

반응형