[UIKit] Concurrency: GCD Basics

Junyoung Park·2022년 12월 25일
0

UIKit

목록 보기
132/142
post-thumbnail

Mastering Concurrency in iOS - Part 1 (Concurrency, GCD Basics)

Concurrency: GCD Basics

구현 목표

  • iOS 환경의 동시성(Concurrency) 복습

Concurrency

  • 동시에 두 개 이상의 사건이 발생하는 경우 → 동시에 여러 개의 명령 시퀀스를 실행하는 것

Parallelism

  • 여러 개의 사건이 병렬적으로 한 번에 실행되는 경우 → 특정 문제를 해결하기 위해 동시에 여러 개의 프로세스를 사용하는 것
  • 더 많은 리소스가 소요되기 때문에 동시성 프로그래밍을 통해 효율적인 리소스 분배 필요

Solution

  • 타임 슬라이싱 + 컨텍스트 스위칭
  • 두 개 이상의 태스크를 처리할 때 각 태스크 당 특정 시간(타임 퀀텀)을 할당, CPU 할당을 스위칭하면서 태스크를 처리하는 기법
  • 타임 슬라이스 크기가 점진적으로 커지는 멀티 레벨의 큐를 통해 효율적으로 사용 가능

Problem

  • 동시성을 해결하기 위한 솔루션으로 인해 문제(주로 읽기 관련 문제)가 생길 수 있고 결과적으로 Data Inconsistency 문제가 일어날 수 있음
  • Dirty Read Problem
  • Unrepeatable Read Problem
  • Lost Update Problem
  • Phantom Read Problem

Concurrency in iOS

  • iOS 환경의 동시성은 멀티 스레딩 주관
  • 직접 스레드를 생성함으로써 멀티 스레딩 구성 가능
  • GCD
  • 오퍼레이션 큐
  • 스위프트의 Modern Concurrency

Manual Thread Creation

  • 커스텀 스레드를 직접 생성 및 컨트롤
  • 스레드 시작, 중단, 딜레이, 스택 사이즈 등 커스텀 가능
class CustomThread {
    func createThread() {
        let thread: Thread = Thread(target: self, selector: #selector(threadSelector), object: nil)
        thread.start()
    }
    
    @objc func threadSelector() {
        print("Custom Thread in action")
    }
}

let customThread = CustomThread()
customThread.createThread()
  • 시스템 환경에 맞춰 스레드를 핸들링해야 함
  • 커스텀 스레드 실행이 종료될 때마다 할당 해제 필요(Deallocation)
  • 관리가 어렵고 메모리 누수가 발생 가능
  • Auto release pool로 커스텀 스레드가 관리되지 않음
  • 실행 순서를 조정해야 함
  • 실제 프로그래밍 과정에서 커스텀 스레드 사용은 좋지 않은 습관

Grand Central Dispatch

  • 디스패치: 시스템이 주관하는 디스패치 큐에 작업을 제출함으로써 멀티코어 하드웨어에서 코드를 동시적으로 실행하는 것
  • FIFO 방식에 따라 workers pools에 클로저를 실행하도록 해주는 API 큐
  • 스레드 풀 중 사용 가능한 풀이 존재한다면 태스크 큐에서 가장 먼저 들어온 작업이 해당 풀로 들어감, 스레드 풀 중 완료된 태스크는 다시 풀에서 나옴
  • 어떤 스레드가 태스크를 실행하는 데 할당될지 결정하는 것은 개발자가 아니라 GCD
  • 적절한 디스패치 큐를 할당함으로써 위의 작업이 실행
  • GCD는 FIFO 방식에 따라 일련의 순서에 따라 / 동시적으로 작업을 수행

Dispatch Queue

  • 큐 상단부 상의 추상화 레이어
  • GCD는 디스패치 큐의 집합을 관리
  • 디스패치 큐는 스레드 풀 상에서 실행되고, 프로그램 태스크는 이러한 (디스패치) 큐에 할당

OS를 다시 복습하는 듯한 느낌. CS의 기본 역학이 당연히 적용되어 있다는 점에서... 언제나 기본이 중요하다는 것을 느낀다!

Order of tasks

  • 어플리케이션의 태스크 할당은 동기적/비동기적 모두 요청 가능
  • Order of Execution: 할당된 작업이 순서에 따라 진행될 것
  • Manner of Execution: 할당된 작업이 어떤 방법으로 실행될 것
  • Synchronous: 동기적. 해당 태스크가 완료될 때까지 실행을 블록시키는 것으로 실행 순서를 보장
  • Asynchronous: 비동기적. 현재 태스크 실행을 지속하면서 동시에 새로운 태스크가 비동기적으로 실행된다는 뜻.

Queues

  • Serial Queue: 한 타임 퀀텀에 하나의 태스크 실행
  • Concurrent Queue: 한 타임 퀀텀에 여러 개의 태스크를 실행. 컨커런트한 큐에 있어서도 작업은 FIFO를 지킴 → 디큐되는 순서가 FIFO에 따라 이뤄진다는 뜻
  • 순차적/동시적 큐는 도착 큐(destination queue)에 영향을 미치는 데, 해당 큐는 현재 디스패칭하고 있는 큐
  • 동기/비동기적 큐는 현재 스레드에 영향을 미치는데, 이 스레드로부터 디스패칭을 하고 있음
var counter = 1
DispatchQueue.main.async {
    // main queue: Serial Queue
    for i in 0...3 {
        counter = i
        print("counter #: \(counter), current Thread: \(Thread.current)")
    }
}

for i in 4...6 {
    counter = i
    print("counter #: \(counter), current Thread: \(Thread.current)")
}

DispatchQueue.main.async {
    counter = 9
    print("counter #: \(counter), current Thread: \(Thread.current)")
}

/*
 counter #: 4, current Thread: <_NSMainThread: 0x6000021301c0>{number = 1, name = main}
 counter #: 5, current Thread: <_NSMainThread: 0x6000021301c0>{number = 1, name = main}
 counter #: 6, current Thread: <_NSMainThread: 0x6000021301c0>{number = 1, name = main}
 counter #: 0, current Thread: <_NSMainThread: 0x6000021301c0>{number = 1, name = main}
 counter #: 1, current Thread: <_NSMainThread: 0x6000021301c0>{number = 1, name = main}
 counter #: 2, current Thread: <_NSMainThread: 0x6000021301c0>{number = 1, name = main}
 counter #: 3, current Thread: <_NSMainThread: 0x6000021301c0>{number = 1, name = main}
 counter #: 9, current Thread: <_NSMainThread: 0x6000021301c0>{number = 1, name = main}
 */
  • 모두 메인 스레드에서 실행되는 구문
  • 디스패치 큐에 비동기적으로 실행되는 0...3, 9는 순서에 따라 처리되기 때문에 실제 프린트되는 순서는 9가 이후에 오는 게 보장이 됨
  • 동기/비동기적으로 처리되는 부분을 체크해보면 4...6이 실행되는 구문과 비동기적으로 실행되는 구문을 비교해야 하는 데, 전자가 코드적으로 하단부에 작성되어 있다 할지라도 순서는 보장하지 않음
profile
JUST DO IT

0개의 댓글