GGURUPiOS

동시성 프로그래밍 - GCD (1) 본문

Swift/동시성 프로그래밍

동시성 프로그래밍 - GCD (1)

꾸럽 2023. 4. 19. 16:33

GCD (Grand central Dispatch)

GCD는 동시성 환경에서 작업을 실행하기 위한 API를 제공함

시스템 수준에서 스레드 및 대기열 관리를 처리함

비동기 실행되는 작업을 쉽게 처리할 수 있고 작업 간의 의존성이나 순서를 지정할 수 있음

DispatchQueue는 GCD를 사용하기 위한 대기열로, 대기열에 작업을 추가해서 작업을 처리하도록 도와줄 것임 (First In, FirstOut)

DispatchQueue에 작업을 넘길 때는 2가지를 정해주어야 함

  • 단일, 다중 스레드 여부 ( Serial / Concurrent )
  • 동기, 비동기 여부 ( sync / async )

DispatchQueue가 GCD와 같은 개념은 아니고, GCD가 더 넓은 개념임

GCD는 Dispatch라는 프레임워크와 같은 개념이라 볼 수 있음

Dispatch 프레임워크에는 DispatchQueue, DispatchWorkItem, DispatchGroup, DispatchQos 등 그 외에도 다양한 타입들이 구현되어 있음

DispatchQueue 를 자세히 알아보자


Serial / Concurrent

Serial 은 단일 스레드에서만 작업

Concurrent는 다중 스레드에서 작업

DispatchQueue 를 초기화할때 attributes를 따로 .concurrent로 지정하지 않으면 기본값은 Serial임

// Serial Queue
DispatchQueue(label: "Serial")
DispatchQueue.main

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

global이 메서드인 이유

→ Main Thread는 메모리에 늘 올라와 있는 기본 스레드임

→ 하지만 Concurrent Queue는 Main Thread외의 새로운 스레드를 만들기 때문에 메서드가 됨

main / global

main과 global은 이미 만들어져있는 큐(대기열)로 main - Serial, global - Concurrent 큐임

main은 전역적으로 사용 가능한 큐 임 (늘 메모리에 있음) (메인 스레드)

main은 serial 큐이기 때문에 동시에 여러 작업을 처리할 수 없음

global에 작업을 추가하면 새로운 스레드를 만들어 그 위에서 작업을 처리함

global 스레드는 메모리에 올라왔다가, 작업이 끝나면 메모리에서 제거됨

Main Thread 란?

메인 스레드는 앱의 기본이 되는 스레드 ( 앱이 실행되는 동안에 늘 메모리에 존재 )

여러개의 스레드를 사용할 때도, 메인 스레드에서부터 필요한 만큼의 스레드가 파생되는 것임

메인 스레드 특징

  1. 전역적인 사용 가능
  2. global 스레드와 다르게 Run Loop 가 자동 설정, 실행
  3. UI 작업은 메인 스레드에서만 가능

위에서 말한

  • 단일, 다중 스레드 여부 ( Serial / Concurrent )
  • 동기, 비동기 여부 ( sync / async )

중 단일, 다중 스레드 여부를 정했으면 동기, 비동기 여부를 정해줘야 함

// 동기
DispatchQueue.main.sync {}
DispatchQueue.global().sync {}

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

위와 같이 작성해 줄 수 있음

sync / async

sync는 동기로 작업한다는 의미. 코드 블럭이 하나의 작업이 됨

async는 비동기로 작업한다는 의미

main.async

만약 위와 같이 코드를 작성했다면 비동기이기 때문에 a b a a b a 이런식으로 출력될 것 같지만, 실제는 a a a a a b b b b b 이런식으로 출력이 됨

→ 단일 스레드에서만 작업이 이루어지기 때문에 DispatchQueue에 쌓인 순서대로 작업이 처리되는 것임

만약 위와 같이 실행하면 어떻게 될까?

b b b b b 가 먼저 출력 될 것 같지만

c,c,c,c,c 가 먼저 출력되곤 함 ( 정확히는 어떤 것이 먼저 출력될지 모름 )

→ 단일 스레드 환경이지만 비동기로 코드 블럭을 호출했기 때문에, 다음 코드로 넘어감

→ 다음 코드가 먼저 실행 되면 c를 호출하는 코드가 먼저 출력 되는 것임

global().async

메인 스레드가 아닌 다른 스레드를 만들어 비동기로 처리하도록 함

여기서 출력이 된 결과를 보면 랜덤하게 ( a b a b b b a a …) 이런식으로 출력이 됨

→ main.async 와는 다르게 새로운 스레드를 생성하기 때문에 ( 위에서는 2개 생성 됨 ) 동시에 작업들이 처리 됨

→ 하지만 어떤 코드가 먼저 실행될지는 알 수 없음 ( async 의 특성 )

global().sync

위와 같이 작성하면 sync 는 동기선언 이기 때문에 a a a a a b b b b b 이런식으로 출력이 된다

두개의 코드블럭은 스레드는 각각 다르지만 동기적으로 일을 처리하기 때문에

각 작업이 끝나기를 기다림

main.sync

main.sync는 직접 호출하면 안되는 코드임

직접적으로 호출하면 deadlock 에 빠지게 됨

sync 는 코드 블럭이 처리되기 전까지 다음 코드로 넘어가지 않음 ( Block-wait )

메인 스레드에서 main.sync 를 호출하게 되면 메인 스레드는 sync의 코드 블럭이 수행되기를 기다려야함

하지만 이 때 sync의 코드 블럭 역시 메인스레드에서 동작하기 때문에 멈춰버림

메인 스레드는 sync가 끝나기를 , sync는 메인스레드의 Block-wait이 끝나기를 기다림

그러나 호출 할 수 있는 경우도 있는데 메인스레드가 아닌 다른 스레드에서 호출하는 경우임

예를들어 global 스레드에서 main.sync를 호출한다면 가능함

DispatchWorkItem

DispatchQueue에서 코드 블럭을 호출할 때, DispatchWorkItem을 활용해서 코드 블럭을 캡슐화 할 수 있음

위의 코드처럼 DispatchWorkItem 으로 묶어서 캡슐화가 가능하다

asyncAfter

asyncAfter는 async 메서드를 원하는 시간에 호출해줄 수 있는 메서드임

DispatchQueue.global().asyncAfter(deadline: .now() + 5, execute: apple

위의 코드는 지금 부터 5초 후에 apple 을 실행 시킨다는 의미임

파라미터의 두 종류 ( deadline, wallDeadline ) 가 있음

deadline : 스톱워치 느낌

wallDeadline : 시스템(기기)의 시간 기준

두 종류로 나눌 수 있으나, 코드 작성법과 동작은 거의 비슷함

asyncAndWait

asyncAndWait 메서드를 사용하면 비동기 작업이 끝나는 시점을 기다릴 수 있음

동작 논리는 사실 sync와 유사함