GCD (Grand Central Dispatch)

yim2627·2021년 12월 25일
2

Swift

목록 보기
32/38

Task Distribution

우린 일을 한다.

아 물론 지금의 난,,, 네,,

이건 간단한 것이니깐 알바를 예로 들어 설명해보겠다.

사장님이 알바생 한명한테만 일을 계속 시킨다.

하지만 알바생은 한번에 하나의 일밖에 하지 못한다..

바닥 닦으면서 불판을 어떻게 닦아..

주어진 일을 하나씩 처리해야하는데, 불판 닦고, 손님 응대하고, 바닥 닦고, 세금 처리까지 다 하고 사장님 애를 봐야한다.

그럼 사장님 애는 알바생에게 먼저 맡겨진 일이 모두 처리될 동안 뭐해야됨

상당히 비효율적일 것이다.

그럼.. 일을 분배하면 되겠지?

알바생을 더 써서 일을 나눈다면 이전보다 훨씬 효율적으로 일을 처리할 수 있을 것이다.

자.. 위에서 든 예에서 알바생을 "스레드"로 바꿔서 생각해보자

메인 스레드에만 할당되어있던 일들을 분배함으로써 메인 스레드가 일을 함과 동시에 다른 스레드들도 일을 하도록 하는 것, 이를 동시성 프로그래밍 이라고 한다.

동시성 프로그래밍을 위해 iOS가 지원하는 대표적인 기술들 Operation, GCD..중 GCD를 알아보자

Grand Central Dispatch

GCD란 멀티 스레드 환경에서 편리하게 개발할 수 있도록 내놓은 기술이다.

스레드 조작, 생성을 OS에서 관리하게 하여, 프로그래머는 그냥 작업을 큐에 넣어주기만 하면 되고, 스레드 생성에 관여하지 않아도 된다.

Serial dispatch queues offer a more efficient alternative to locks and other synchronization primitives.

처럼 다른거 쓰지말고 우리가 만든 좋은 것 쓰라고 애플이 추천하고.. 있는 기술이다.

아까 큐에 넣어준다고 그랬는데, 그 큐가 GCD에서 사용하는 Dispatch Queue이다.

정리하자면, Dispatch Queue에 작업을 추가하면 GCD가 적합한 스레드에 자동으로 작업을 할당하여 스레드를 활성화 시키고, 해당 작업이 종료되면 스레드를 제거한다.

따로 설정을 하지 않으면, 모든 코드는 메인 스레드에서 시작되는데 메인 스레드에서 Dispatch Queue로 작업을 보낼 때 동기적으로 보낼지, 비동기적으로 보낼지를 선택할 수 있다.

진짜 핵심인 동기, 비동기를 알아보자

sync

메인 스레드는 Dispatch Queue로 작업을 보내고, 어떤 행동을 취할지 선택할 수 있다.

그 중 sync, 동기의 개념은 큐로 보낸 작업이 끝날 때까지 기다리고, 작업이 끝나면 다음 작업을 실행하는 것이다.

예제 코드를 보자

DispatchQueue.global().sync {
    print("Task 1")
    for i in 0...5 {
        print("\(i)초")
        Thread.sleep(forTimeInterval: 1)
    }
}

DispatchQueue.global().sync {
    print("Task 2")
    Thread.sleep(forTimeInterval: 1)
}

DispatchQueue.global().sync {
    print("Task 3")
}

간단하다.

메인스레드는 Task 1을 큐에 보내고 해당 Task가 처리될 때까지 아무것도 하지 않고 오로지 처리되기만을 기다린다.

처리된 후 다음 Task가 진행되는 것이다.

그림도 간단하게 그려봤다.

async

sync는 메인 스레드가 큐에 작업을 보내고, 그 작업 처리가 끝날 때까지 다음 작업을 하지않고 기다렸지만, async, 비동기는 큐에 작업을 보낸 후 그 작업에 대해선 신경쓰지 않는다.

큐에 보낸 작업은 알아서 처리되게끔 놔둬버리고, 자기 할일을 하는 것이다.

아니 비동기 이건 그림 그리기 너무 힘들어서 말로 설명함

메인 스레드: 일하자~~ 첫번째 누구야 Async네 큐로 보낼테니 알아서해. 다음

메인 스레드: 두번째 누구야 Async네 큐로 보낼테니 알아서해. 다음

메인 스레드: 일하자~~ 첫번째 누구야 Async네 큐로 보낼테니 알아서해. 다음

이게 다임

그렇다면 비동기 처리가 필요한 이유가 뭘까?

한 어플리케이션이 실행될 때 외부에서 이미지를 받아와 화면에 출력한다고 생각해보자.

외부에서 이미지를 받아오는 행위를 동기적으로 하게된다면 어플리케이션은 그 이미지가 모두 다운로드될 때까지 그냥 멈춰있는다.

당연히 이러면 안되겠지?

이럴 때 비동기적으로 작업을 하여 "나는 내 할일 할테니깐 이미지 다운 완료되면 이미지 내놔" 하고 처리하는 것이다.

Serial Queue, Concurrent Queue

위에서 우린 global Queue에 작업을 동기로 보내거나, 비동기로 보내거나 했었다.

우리가 큐로 작업을 보내면 그 큐는 해당 작업을 처리하기 위해 특정 스레드에 작업을 보낸다.

여기서 큐의 속성에 따라 작업을 단일 스레드로 처리할 것인지, 다중 스레드로 처리할 것인지 결정된다.

큐의 속성은 Serial(직렬), Concurrent(동시)가 있고, 먼저 Serial Queue에 대해 알아보자

Serial Queue

한번에 한가지 일만 처리할 수 있기때문에 하나의 작업이 시작되고 끝나면 다음 작업이 시작되므로 작업의 순서가 정해져있다.

Serial Queue에 작업을 배치시키면 큐는 다른 하나의 스레드에 작업을 할당하여 처리한다.

여기서 헷갈릴 수 있는 것은..

SerialQueue.async {
	// Task
}

아니 async는 안기다린다며? 할 수 있는데

그건 메인 스레드(보통 모든 코드가 시작되는 스레드)가 기다리고 안기다리고인 것이지

메인 스레드가 큐에 작업을 보내고, 큐가 작업 처리를 위해 특정 스레드에 보내는 것이랑은 관련이 없다.

큐는 FIFO 특성을 가지기 때문에 오로지 하나의 스레드에서만 작업을 처리하는 Serial Queue의 경우 큐에 일이 먼저 들어온 순서대로 하나의 스레드에 할당하여 일을 처리하는 것이다.

Concurrent Queue

Concurrent Queue에 작업을 배치시키면 큐는 다른 여러 스레드에 작업을 할당하여 처리한다.

어떤, 몇개의 스레드에 작업을 분산 시킬지는 OS가 알아서 결정한다.

Serial Queue vs Concurrent Queue

어떤 큐를 사용할 것인지는 작업의 순서가 필요한가에 따라 결정하면 된다.

너무 막연한 예시지만 이미지를 받아오고, 그 이미지를 UI에 내보낸다! 라고 생각해보자

해당 작업을 Concurrent Queue로 처리한다면 여러 스레드로 작업이 분배되지만 각 작업들이 끝나는 시간을 알 수 없기 때문에 큐에서의 작업 순서대로 처리되지 않는다.

다 제각각의 작업 처리 속도에 따라 다르게 나오는 것이다.

하지만 Serial Queue의 경우 큐에 담긴 작업들이 하나의 스레드에만 할당되고, 하나의 작업이 끝나길 기다렸다가 다음 작업이 하나씩 실행되기 때문에 작업의 시작과 종료 시점을 알 수 있다.

그러므로 위같은 순서가 중요한 작업은 큐에 늦게 들어온 작업이 먼저 끝나는 상황 등을 방지하기 위해 Serial Queue에 보내 하나의 스레드에서 순서대로 처리되게 해야한다.

위 내용 정리

async의 비교 대상은 sync고, Concurrent의 비교대상은 serial이다.

async/sync와 Concurrent/Serial을 연관 짓지 말자.

async/sync은 작업을 특정 큐에 보내고 그 작업이 끝나길 기다릴지, 말지에 대한 것이다.

Concurrent/Serial은 특정 큐로 보낸 작업들을 하나의 스레드로 처리할지, 여러 스레드로 처리할지에 대한 것이다.

위 두가지를 조합해보면

SerialQueue.sync : 메인 스레드에서 Serial Queue로 작업을 넘기고 해당 작업이 끝날 때까지 기다린다. (sync) / Serial Queue로 넘겨진 작업은 하나의 스레드에서 처리되기 때문에 하나의 스레드에서 먼저 처리되고 있는 작업이 있다면 그 작업이 끝나고 작업이 시작된다. (Serial Queue)

SerialQueue.async : 메인 스레드에서 Serial Queue로 작업을 넘기고, 바로 다음 작업을 시작한다. Serial Queue로 넘긴 작업은 신경쓰지 않는다. (async) / Serial Queue로 넘겨진 작업은 하나의 스레드에서 처리되기 때문에 하나의 스레드에서 먼저 처리되고 있는 작업이 있다면 그 작업이 끝나고 작업이 시작된다. (Serial Queue)

ConcurrentQueue.sync : 메인 스레드에서 Concurrent Queue로 작업을 넘기고, 해당 작업이 끝날 때까지 기다린다. (sync) / Concurrent Queue로 넘겨진 작업들은 여러 스레드로 분산되어 처리되기 때문에 Queue에 먼저 들어온 작업이 할당된 스레드와 다른 스레드에서 처리될 수 있으므로 해당 작업(Queue에 먼저 들어온)이 모두 끝나지 않아도 시작된다. (순서가 없음/Concurrent)

ConcurrentQueue.async : 메인 스레드에서 Concurrent Queue로 작업을 넘기고, 바로 다음 작업을 시작한다. Serial Queue로 넘긴 작업은 신경쓰지 않는다. (async) / Concurrent Queue로 넘겨진 작업들은 여러 스레드로 분산되어 처리되기 때문에 Queue에 먼저 들어온 작업이 할당된 스레드와 다른 스레드에서 처리될 수 있으므로 해당 작업(Queue에 먼저 들어온)이 모두 끝나지 않아도 시작된다. (순서가 없음/Concurrent)

Dispatch Queue 종류

Dispatch Queue는 3가지 종류가 있다.

  1. Main Queue
  2. Global Queue
  3. Custom Queue

Main Queue

DispatchQueue.main.sync {
	//Task
}

DispatchQueue.main.async {
	//Task
}

위 코드중 첫번째 코드는 에러난다. 자세한 건 여기 참고

Main QueueMain Thread단 하나만 존재하며, 해당 큐에 할당된 작업들은 모두 Main Thread에서 처리된다.

Main Queue에 할당된 작업을 처리할 수 있는 스레드는 Main Thread 단 하나밖에 존재하지 않기때문에 작업을 분산시키지 못한다.

작업을 여러 스레드로 분산시키지 못한다? -> 하나의 스레드에서만 처리한다? -> 단일 스레드를 다루는 큐 -> Serial Queue

이런 이유로 Main QueueSerial 한 속성을 가지게 된다.

Global Queue

DispatchQueue.global().sync {
	//Task
}

DispatchQueue.global().async {
	//Task
}

Global Queue는 Concurrent한 속성을 가진 큐이다.

Concurrent한 큐는 작업의 순서를 중요하기 여기지 않지만, 작업의 우선 순위는 매길 수 있다.

우선 순위는 Qos라는 것으로 매길 수 있는데.. 큐의 우선 순위가 높을 수록 더 많은 스레드를 배치해 처리한다.

이정도만 알아놓자.. Qos에 대한 자세한 건 다음에.. 힘듦

Custom Queue

우리는 큐를 직접 만들 수도 있다.

큐의 속성도 지정할 수 있고, 커스텀 큐의 default 속성은 Serial 이다.

큐의 속성은 지정할 수 있다는 말은 Concurrent 속성으로 큐를 만들 수 있다는 말이고, Concurrent 속성인 큐를 만들 수 있다는 말은 큐의 Qos(우선 순위)도 정할 수 있다는 말이겠지?

Custom Serial Queue

let customQueue1 = DispatchQueue(label: "Jiseong1")

Custom Concurrent Queue

let customQueue2 = DispatchQueue(label: "Jiseong2", attributes: .concurrent)

let customQueue3 = DispatchQueue(label: "Jiseong3", qos: .utility, attributes: .concurrent) 

후어... 황이팅

profile
여러 사람들과 함께 많은 것을 배우고 나누리

0개의 댓글