[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개의 댓글

관련 채용 정보