본문 바로가기

iOS

iOS-Concurrency

728x90

해당 글은 인프런-엘런 님의 강의를 듣고 정리한 글입니다.

 

GCD에 들어가기 전에 관련 개념을 정리하고 들어가겠습니다.

✅ 쓰레드란

평소 PC 사양을 표현할 때 많이 사용하는 개념입니다. 4 코어 8 스레드, 8 코어 16 스레드 등..으로 많이 사용합니다.

이런 예시를 통해서 짐작할 수 있는 것처럼, 스레드는 컴퓨터의 일을 처리하는 부분입니다. 

 

✔️ 스레드에게 일을 잘 분배하지 않았거나,

✔️ 쓰레드가 일을 잘 처리하지 못하고 있다면

>> 버벅거리는 현상이 나타납니다.

 

🤔 그렇다면 왜 버벅이는 현상이 나타날까요?

예를 들어 6 코어라고 가정을 해보겠습니다. 6 코어라면 최소한 12 스레드가 있을 것입니다. 앞서 스레드는 일하는 부분이라고 했습니다. 12 스레드의 의미는 간단하게 일하는 부분이 총 12개라고 생각하면 됩니다. 

총 12개에서 컴퓨터의 일을 작업할 수 있음에도 불구하고, 1개의 스레드만 일하고 있다면? 그리고 해당 스레드가 UI도 관리하는 스레드라면? 해야 하는 일은 많은데 그 일을 처리할 곳이 오직 1개이므로 버벅거리는 현상이 나타나게 됩니다. 

 

🤔 왜 이제야 그런 현상이 나타날까요?

그 전에는 버벅거리지 않았는데, 어느 순간부터 딜레이가 된다면, 그 전에는 그만큼 큰 일을 시키지 않았기 때문입니다.

예를 들어, 이미지 파일을 테이블 뷰 셀에 올리는 작업을 생각해봅시다. 이 작업을 위해서는 아래와 같은 과정을 거치게 됩니다.

1. 네트워크를 통해 이미지 파일을 (압축 파일) 다운로드 한 뒤, 

2. 압축 파일을 해제합니다.

3. 이미지 파일을 해당 화면에 맞게 변형을 하고 

4. 테이블 뷰 셀에 이미지를 표시합니다.

-> 그리고 이런 셀은 여러 개 존재하게 됩니다. 그러면 그만큼 1->4의 과정이 반복되겠죠? 결국 소화하지 못할 만큼의 일을 시키고 있으므로 부하가 걸리게 됩니다.

>> 각 작업이 오래 걸릴 뿐 아니라,

>> 리프레쉬를 해야 하는데 그러지 못해서 버벅거리는 현상이 나타나는 것입니다.

 

❌ 복사기를 여러 개 샀음에도 불구하고, 한 대의 복사기에서만 작업을 처리하고 있어서 딜레이가 된 것이죠. ❌

 

✅ 동시성 프로그래밍의 목적

동시성 프로그래밍은 위와 같은 과정이 발생하지 않도록 하기 위해 필요합니다.

즉, 어떻게 일을 여러 스레드에 분산시킬 수 있을까? 

= 작업을 어떻게 분산시킬 수 있을까?

= Task를 어떻게 다른 스레드에서 동시에 일을 하게 할 수 있을까? 

를 고민하는 과정이 동시성 프로그래밍의 목적입니다. 

 

그리고 iOS에서는 Task를 대기 행렬에 보내기만 하면, 알아서 OS가 다른 스레드로 이들을 분산시켜 줍니다. 

여기서 말하는 대기 행렬은 큐를 의미하고,

큐는 먼저 들어온 순서대로 배치됩니다. (FIFO) 주의할 점은 먼저 들어왔다고 해서, 먼저 배치되었다고 해서, 먼저 작업이 끝나는 것은 아닙니다.

 

그러므로 개발자가 할 일은 Task를 Queue에 보내자.입니다. 다시 말해 큐로 보내는 코드를 배우자.입니다. 

 

대기 행렬 

GCD / Operation

iOS 프로그래밍에서 대기 행렬, 큐라고 표현할 수 있는 것의 종류는 크게 2가지가 있습니다. 

1. GCD (= Dispatch Queue)

2. Operation 

 

이 두 가지는 다음과 같은 특징을 갖고 있습니다.

✔️ 직접적으로 스레드를 관리하지 않고 큐를 이용해서 분산 처리합니다.

  • 직접 쓰레드를 생성/관리하는 것은 하드웨어 or 일의 부하와 같은 시스템 지식 없이 사용하게 될 시, 오히려 앱이 더 느려질 수 있습니다.)

✔️ GCD/Operation을 사용해서 시스템에서 알아서 쓰레드 숫자를 관리합니다.

✔️ 스레드보다 높은 차원/레벨에서 작업을 한다고 볼 수 있습니다.

✔️ 다른 스레드에서 오래 걸리는 작업들이 비동기적으로 동작할 수 있도록 만들어줍니다.  

  • 보통 여기서 말하는 오래 걸리는 작업은 통신과 관련된 부분을 말합니다.

 

큐로 보내는 방법

DispatchQueue.global().async {
    // 보낼 작업물
}

위의 코드를 하나씩 살펴보면 아래와 같습니다. ⬇️

- Dispatch : 보내다

- global() : 글로벌 큐에

- async : 비동기적으로 

- 클로저 안의 코드 : 보낼 작업물 

 

즉, "작업물을 글로벌 큐에 비동기적으로 보낼 거야."라는 의미입니다.

(이때, 클로저 안의 작업이 작업의 한 단위가 됩니다.)

 

func secondTask0() -> String {
    return "★ 작업1, "
}

func secondTask1(inString: String) -> String {
    return inString + "작업2, "
}

func secondTask2(inString: String) -> String {
    return inString + "작업3 ★"
}


// 순서가 필요한 작업 실행해보기
DispatchQueue.global().async {
    let out0 = secondTask0()
    let out1 = secondTask1(inString: out0)
    let out2 = secondTask2(inString: out1)
    print(out2)
}

위와 같은 코드가 있을 때, 작업 1/작업 2/작업 3이 순차적으로, 그리고 각 클로저 안의 묶음이 하나의 작업이 되어 진행됩니다. 이런 작업을 큐로 보내면, iOS가 알아서 작업 1/작업 2/작업 3을 다른 스레드로 분산시킵니다. 

 

GCD VS Operation 

둘의 차이는 무엇이고, 어떤 것을 써야 할까요?

 

GCD 

  • 디스패치 큐입니다.
  • 주로 클로저로 묶일 수 있는, 간단한 작업을 처리할 때 사용합니다.
  • 메서드 위주로 함수를 사용하는 작업입니다. 

 

Operation

GCD를 기반으로 만들어졌습니다. GCD에서 여러 처리를 하면서 다른 기능이 필요하여 만들어진 것으로 취소, 순서 지정, 일시 중지 등의 기능이 추가되었습니다. 

  • 오퍼레이션 큐입니다.
  • 주로 복잡한 일에 쓰입니다.
  • 데이터와 기능을 캡슐화한 객체입니다.

 

🤔 어떤 것을 써야 하는가?

진리의 케바케입니다. 정해진 답은 없습니다. 

그러나, 프로젝트의 효율성 및 적합성에 따라서 각 대기 행렬에 다른 기능을 적용해야 합니다. (각 큐에 더 적합한 기능이 있기 때문에)

 

비동기/동기

비동기 (async)

작업을 보내고, 작업의 완료와 상관없이(작업의 완료를 기다리지 않고) 바로 리턴하는 것을 말합니다. 

✔️ 일을 보내고, 끝나는 것에 관심 없으므로 오래 걸리는 작업이 사라졌기 때문에 다른 일을 할 수 있습니다.

✔️ 일을 시작시키고 작업이 끝날 때까지 기다리지 않습니다. 즉, 메인 스레드가 다른 일을 시작할 수 있습니다. 

 

DispatchQueue.global().async {
    
}

위와 같은 코드를 해석하면 아래와 같습니다. ⬇️

- 원래의 작업이 진행되고 있던 곳 (= 메인 스레드)에서 디스패치 글로벌 큐로 작업을 보내고

- 작업(클로저 안의 코드)을 기다리지 않습니다. 

 

동기 (sync)

'싱크가 맞는다, 맞지 않는다.'의 개념을 생각하면 됩니다.

작업을 보내고 일을 마칠 때까지, 작업이 완료될 때까지 기다립니다. 

 

DispatchQueue.global().sync {
    
}

위와 같은 코드를 해석하면 아래와 같습니다. ⬇️

- 원래의 작업이 진행되고 있던 곳 (= 메인 쓰레드)에서 디스패치 글로벌 큐로 작업을 보내고

- 작업(클로저 안의 코드)을 기다립니다.

 

🤔 동기 처리를 한다면, 스레드로 보내는 것이 의미가 있는가?

어차피 같은 스레드라면, 작업이 끝날 때까지 기다려야 하는데 다른 스레드로 보내는 것이 의미가 있을까요? 

➡️ 그래서 실질적으로 동기 처리를 하게 된다면, 다른 쓰레드로 보내지 않습니다.

(주로 async를 사용하고 상황에 따라서 응용적으로 sync를 사용합니다.)

 

 

📌 결론 📌

✔️ 비동기 - 작업을 다른 스레드에서 하도록 시킨 후 작업이 끝나기를 기다리지 않고 다음 일을 진행

✔️ 동기 - 작업을 다른 쓰레드에서 하도록 시킨 후 작업이 끝나기를 기다렸다가 다음 일을 진행 

 

✅ 비동기 개념이 필요한 이유?

대부분 서버 통신의 이유가 큽니다. (오래 걸리는 작업이 많기 때문에)

그래서 URLSession의 코드를 봐서 알 수 있듯이, 네트워크와 관련된 작업들은 내부적으로 비동기적으로 구현되어 있습니다. 

 

Serial/Concurrency 

두 가지 모두 큐가 갖고 있는 특징입니다.

 

Serial Queue (직렬 큐)

큐에 들어온 작업을 단 하나의 다른 스레드로 보내는 작업을 말합니다.

 

Concurrent Queue (동시 큐)

큐에 들어온 작업을 다른 여러 쓰레드로 보내는 작업을 말합니다.

몇 개의 쓰레드로 분산할지는 시스템(OS)이 알아서 정합니다. 다만, 스레드가 여러 개로 분산되는 것은 동일합니다. 

 

📌 결론 📌

✔️ 직렬 큐 - 다른 한 개의 스레드에서, 보통 메인에서 분산시킨 작업을 다른 한 개의 스레드에서 처리하는 큐 

✔️ 동시 큐 - 다른 여러 개의 스레드에서, 보통 메인에서 분산시킨 작업을 다른 여러 개의 스레드에서 처리하는 큐 

 

✅ 동시 큐가 무조건 좋아 보이는데, 왜 직렬 큐가 필요할까? 

직렬 큐의 경우, 순서가 중요한 작업에서 사용하고

동시 큐의 경우, 각자 독립적이지만 유사한 여러 개의 작업을 처리할 때 사용합니다. 

 

❓ 비동기 == 동시

➡️ ❌❌❌❌❌

비동기는 작업을 보내는 시점에서 해당 작업을 기다릴지 말 지에 대한 것을 다루는 것을 말하고, 

동시는 대기 열로 보내진 작업이 여러 개의 스레드로 갈 것인지, 아니면 하나의 쓰레드로 갈 것인지에 대한 것을 다룹니다. 

'iOS' 카테고리의 다른 글

Apple Developer Pending  (0) 2022.04.19
Autolayout Animation  (0) 2022.04.17
SwiftGen  (0) 2022.04.05
UIStackView  (0) 2022.04.04
ClipsToBounds vs MasksToBounds  (0) 2022.03.29