EEYatHo 앱 깎는 이야기

Swift ) Concurrency, Thread, GCD - EEYatHo iOS 본문

iOS, Swift/Swift Theory

Swift ) Concurrency, Thread, GCD - EEYatHo iOS

EEYatHo 2023. 1. 12. 11:32
반응형

 

Concurrency


CPU 칩은 발달을 거듭해 열과 같은 물리적 법칙 한계로 코어당 성능을 더이상 끌어올리기 힘들어졌고,

칩이 가지고 있는 코어를 늘리는 방향으로 발전하고 있다.

 

코어의 갯수는 계속해서 늘어날 것으로 예상된다.

이에 소프트웨어 개발자는 멀티코어(동시성) 프로그래밍을 어떻게 잘 할 수 있을지 고민해야한다.
애플 문서 링크

iPhone 14 CPU 코어 6개

 

 

Thread


개발자가 여분의 코어를 제어하는 전통적인 방법은 Thread(쓰레드)를 사용하는 것이다.

쓰레드를 생성하고 해제하면서 여분의 코어를 이용해 병렬적으로 Task를 처리한다.

스케쥴링은 CPU가 알아서 해준다.

let thread = Thread {
    print("My Awesome Thread")
}
thread.start()

이 방법의 문제는 쓰레드를 몇개 만드는게 좋은가?

너무 적으면 효과적인 병렬처리를 할 수 없고, 너무 많으면 Context-Switching 오버헤드가 커딘다.

 

앱의 최적의 쓰레드 갯수는 시스템 상황(부하)과 하드웨어에 의존되기에 계산하기 어렵다.

만약 계산한다고 해도, 수백 수천개의 쓰레드를 일일히 생성하고, 서로 간섭되지 못하게하고, 해제하는 것은 매우 힘든일이다.

이는 성능 향상을 보장하지 않으면서, 소프트웨어 설계에 복잡성과 위험을 추가로 가져온다.

 

다행히도 Apple 의 운영체제 (iOS, OS X 등) 에서 해결책을 제공한다.

바로 GCD ( Grand Central Dispatch ) 이다.

 

쓰레드를 일일히 관리할 필요없이, Task 만 Queue 에 던져주면 되는 인터페이스를 제공한다.

Task 를 적절히 쓰레드에 분배하는 것은 GCD 가 해준다. 

 

 

 

GCD


GCD에는 참 다양한 키워드가 있다.

 

DispatchQueue

- main, global, custom

- serial, concurrent

- sync, async

- QoS

- flags

- DispatchWorkItem

- DispatchGroup

  - enter, leave

 

OperationQueue

- Operation

- Operation State
- Dependency

 

DispatchSemaphore

- wait, signal

 

DispatchSources

 

 

 

DispatchQueue


  • GCD 에서 Task 를 넣는 Queue 의 한 종류. 
  • Dispatch 내장 라이브러리에 존재

 

  • 사용 방법 예시
DispatchQueue.main.async {
    // Task
}

 

  • DispatchQueue 사용하는 쓰레드 수에 따른 분류 : serial, concurrent
    • serial : 하나의 쓰레드를 사용하여 Task 들을 처리. 직렬.
    • concurrent : 시스템 여유에 따라 여러개의 쓰레드를 사용하여 Task 들을 처리. 병렬.

 

  • DispatchQueue 의 종류 : main, global, custom
    • main : 메인 큐. 메인 쓰레드를 사용. serial
    • global : 기본으로 제공해주는 concurrent 큐. 
    • custom : 개발자가 만들 수 있음. 기본적으로 serial 이지만, concurrent 로 설정 가능
DispatchQueue.main.async {
    // Task
}

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

let customQueue = DispatchQueue(label: "myQueue", attributes: .concurrent)
customQueue.async {
    // Task
}

 

  • DispatchQueue 에 Task 를 넣는 방법 : sync, async
    • sync : 넣은 Task 가 끝나야 다음 코드를 실행. 동기.
    • async : 넣은 Task 가 끝나는 여부와 상관없이, 다음 코드를 실행. 비동기
DispatchQueue.global().sync {
    // Task
}

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

 

 

  • DispatchQueue Task 의 우선순위 정하기 : QoS (Quality Of Service)
    • 6가지 우선순위를 제공. concurrent 큐에서 Task 들을 병렬 처리할 때, 우선순위가 높은 Task 를 우선적으로 진행.
      • userInteractive : 애니메이션 같은 즉각적인 반응을 해야할 때 사용
      • userInitiated : 파일 열기 처럼 즉각적인 반응을 해야하지만 몇초 걸리는 작업
      • default : 일반적인 작업. 이름이랑 다르게 기본값이 아님.
      • utility : 데이터 다운로드나 프로그래스바 같은 네트워킹. 몇초~몇분 걸리는 작업
      • background : 동기화나 백업처럼 유저가 인지하지 못하는 작업
      • unspecified : QoS가 설정되지 않았다는 뜻. 얘가 진짜 기본값
DispatchQueue.global(qos: .userInteractive).async {
    // Task
}

let customQueue = DispatchQueue(label: "myQueue", qos: .userInitiated)
customQueue.async {
    // Task
}

 

 

  • flags ( DispatchWorkItemFlags )
    • QoS 및 장벽을 생성할지 또는, 분리된 새 스레드를 생성할지 여부를 정함
    • 나중에 공부하기

 

  • Closure이 아닌 DispatchWorkItem 을 넣는 식으로 사용 가능
let item = DispatchWorkItem(qos: .userInteractive) {
    // Task
}
DispatchQueue.main.async(execute: item)

 

  • 여러 Task 가 다 끝난 타이밍 알기 : DispatchGroup
    • group 에 Task 를 넣기전에 notify 를 설정하면, notify 블록이 바로 실행되어버림
    • enter, leave 를 이용해서 작업이 끝나는 타이밍을 개발자가 컨트롤 가능 ( 비동기 대응 )
let group = DispatchGroup()

let item = DispatchWorkItem(qos: .userInteractive) { }
DispatchQueue.main.async(group: group, execute: item)
DispatchQueue.global().async(group: group, qos: .background) { }

group.notify(queue: .global()) {
    print("작업 2개 완료")
}
let group = DispatchGroup()

let item = DispatchWorkItem(qos: .userInteractive) {
    NetworkTask(completion: { [weak self] in
        self?.group.leave()
    })
}
        
group.enter()
DispatchQueue.main.async(group: group, execute: item)
        
group.enter()
DispatchQueue.global().async(group: group, qos: .background) {
    NetworkTask2(completion: { [weak self] in
        self?.group.leave()
    })
}

group.notify(queue: .global()) {
    print("작업 2개 완료")
}

 

 

 

OperationQueue


  • GCD 에서 Task 를 넣는 Queue 의 한 종류. 
  • DispatchQueue 보다 복잡한 작업 (종속성을 쉽게 관리할 수 있고, 상태관리도 할 수 있음) 이 가능함
    하지만 성능면에서 좋지 않고, 대부분의 작업이 OperationQueue 만큼의 복잡한 관리를 요구하지 않음
    ( OperationQueue 수준의 관리가 필요할 정도면 코드 구조가 잘못되었을 가능성이 높음 )

 

  • Foundation 내장 라이브러리에 존재

 

  • 사용법
let opQeueu = OperationQueue()
opQueue.addOperation {
    // Task
}

 

  • 메인 쓰레드 접근
OperationQueue.main.addOperation {
    // Task
}

 

  • Operation 을 생성해서 넣는식으로도 사용 가능
    • Operation 을 SubClassing 하고, main 함수를 오버라이딩하여 사용
    • BlockOperation : Operation 을 상속받아서, 단순하게 Block(클로저) 를 받아서 생성할 수 있게 만든 구현체
class MyOperation: Operation {
    override func main() {
        // Task
    }
}
let op = MyOperation()
OperationQueue.main.addOperation(op)
let op = BlockOperation {
    // Task
}
OperationQueue.main.addOperation(op)

 

  • Operation 은 여러 상태값이 존재

 

  • waitUntilFinished
    • DispatchQueue 의 sync, async 와 같은 개념
    • true 면 Queue 에 넣은 Operation 이 끝나야 다음 코드 실행
    • false 면 Queue 에 넣은 Operation 이 끝나든 말든 다음 코드 실행 ( Queue 에 넣은 후, 호출자에게 스레드를 반환 ) 
let op = BlockOperation {
    // Task
}
opQueue.addOperations([op], waitUntilFinished: true)

 

  • Operation 은 의존성을 쉽게 관리할 수 있음 ( A Operation 이 끝나야 B Operation 이 실행되도록 )
let op = BlockOperation {
    // Task
}
let op2 = BlockOperation {
    // Task
}
op2.addDependency(op)

let opQueue = OperationQueue()
opQueue.addOperations([op, op2], waitUntilFinished: false)

 

 

 

DispatchSemaphore


  • 임계구역 ( Critical Section ) 관리를 위한 객체
  • 동시에 작업 가능한 쓰레드의 갯수를 정하고 관리
    • wait : Semaphore 에게 작업 시작을 요청
      ( Semaphore 값이 0이면 양수가 될 때 까지 대기. 양수면 1을 빼고 작업 시작 )
    • signal : Semaphore 에게 작업 완료를 알림 ( Semaphore 값을 1 더함 )
let semaphore = DispatchSemaphore(value: 2)

DispatchQueue.global().async {
    semaphore.wait() // 세마포어에 여유가 생길 때 까지 대기. 세마포어 값 - 1
    // 임계 구역 접근 Task
    semaphore.signal() // 세마포어에 작업 완료 알림. 세마포어 값 + 1
}

DispatchQueue.global().async {
    semaphore.wait()
    // 임계 구역 접근 Task2
    semaphore.signal()
}

DispatchQueue.global().async {
    semaphore.wait()
    // 임계 구역 접근 Task3
    semaphore.signal()
}

 

 

 

DispatchSources


 

 

Reference


 

 

Introduction

Introduction Concurrency is the notion of multiple things happening at the same time. With the proliferation of multicore CPUs and the realization that the number of cores in each processor will only increase, software developers need new ways to take adva

developer.apple.com

 

iOS ) Concurrency Programming Guide - Concurrency and Application Design

안녕하세요 :) Zedd입니다.오늘은..!!!! Concurrency Programming에 대해서 알아볼건데 역시나 Apple문서를 일단 읽고 시작하겠습니다.원문은 을 참고해주세요 :) Concurrency Programming Guide Introduction동시성(Concu

zeddios.tistory.com

 

[Swift]DispatchQueue와 스레드의 개념

Xcode의 코드들을 접하다 보면 이런 코드를 보았을 것이다. DispatchQueue.main.async { self.tableView.reloadData() } 결론적으로는 'main thread에서의 비동기 실행을 위한 코드'이다. 어떤 상황에서 사용되며, Dispa

weekoding.tistory.com

 

Swift Concurrency에 대해서

안녕하세요. iOS Platform Dev 팀의 김윤재입니다. 1편에서 설명드렸던 것과 같이, 온보딩 스터디에서 공부한 내용 중 저에게는 동시성 프로그래밍(concurrency)에 관한 내용이 가장 도움이 되었습니다.

engineering.linecorp.com

 

[iOS] 차근차근 시작하는 GCD — 1

이번엔 제발 이해하고 싶다 GCD..🥂

sujinnaljin.medium.com

 

[iOS - swift] Operation, OperationQueue, 동시성

Operation single task에 관한 데이터와 코드를 나타내는 추상 클래스 해당 클래스를 서브클래싱하여 사용하면 안정적으로 task를 실행시킬 수 있는 효과 존재 OperationQueue Operation 객체들을 priority에 의

ios-development.tistory.com

 

iOS & Swift 공부 - Operation Queue

연산(Operation)의 실행을 관리하고 대기열의 동작관리를 하는 Operation Queue스위프트에서 Queue 를 볼 때마다 Main Queue Operation (ex. UI updates) 인지, 아니면 Background Queue Operation (ex

velog.io

 

Comments