[iOS] Concurrency(동시성) 프로그래밍

Inwoo Hwang·2021년 8월 26일
1

iOS

목록 보기
5/13
post-thumbnail

"본 내용은 인프런강의를 들으며 DispatchQueue와 Operation에 대해 간단하게 정리한 글 입니다."

Concurrency(동시성) 프로그래밍


목차

  1. GCD, Operation에 관하여
  2. DispatchQueue(GCD)의 종류와 특성
  3. Operation에 대해

GCD, Operation에 관하여


GCD, Operation

GCDOperation
Grand Central Dispatch = Dispatch QueueOperation Queue
- 간단한 일(커뮤니케이션의 양)- 복잡한 일(커뮤니케이션의 양)
- 함수를 사용하는 작업(메서드 위주)- 데이터와 기능을 캡슐화한 객체

Operation이라는 개념은 GCD의 개념을 기반으로 여러가지 기능이 더해져서 만들어진 개념이다.

  • 취소 / 순서지정 / 일시중지(상태추적)과 같은 기능 추가

iOS에서는...

  • iOS에서는 직접적으로 쓰레드를 관리하지 않고, Queue(대기열/대기행렬)라는 개념을 이용해 작업을 분산처리한다.
  • GCD / Operation을 사용해 시스템에서 알아서 쓰레드 숫자를 관리한다.
    • (직접 쓰레드를 생성하는 것은 하드웨어나 일의 부하(load)와 같은 시스템에 대한 지식없이 사용하면, 오히려 앱이 느려질 수 있다.)
  • 쓰레드 보다 더 높은 레벨/ 차원에서 일을한다.
  • 쉽게 다른 쓰레드에서 (오래걸리는) 작업들이 "비동기적으로 동작" 하도록 만들어 준다.
    • (어떤 API들은 내부적으로 다른 쓰레드에서 비동기적으로 실행되도록 설계되어 있다.)

Queue로 보내는 방법(Code)

//Queue로 클로저를 보낸다. 글로벌큐에(initializer). 비동기적으로
DispatchQueue.global().async {
  작업한 내용을(클로저)
}

// 보낼 queue의 종류를 선언
let queue = DispatchQueue.global()
// 해당 queue에 비동기적으로 보낼 것이다.
queue.async {
  클로저(closure)}
// 작업의 단위 ===> "하나의 클로저" 안에 보내는 작업 자체가 묶이는 개념
DispatchQueue.global().async {
  print("Task1 시작")
  print("Task1 의 중간작업 1")
  print("Task1 의 중간작업 2")
  print("Task1 의 중간작업 3")
  print("Task1 의 중간작업 4")
  print("Task1 의 중간작업 5")
  print("Task1 종료")
}

Synchronous vs Asynchronous

동기(Sync) 와 비동기(Async) 개념

비동기 (Async)

메인 thread가 다른 thread에서 작업을 하도록 시킨 후 해당 작업이 끝나길 기다리지 않고 다음 작업을 진행한다.

→ 메인 thread가 다른 일 처리를 시작할 수 있다.

동기(Sync)

메인 thread가 다른 thread에서 작업을 하도록 시킨 후 해당 작업이 끝날 때 까지 기다렸다가 다음 작업을 진행한다.

→ 작업이 끝나기 전까지 메인 thread는 다른 일처리를 시작할 수 없다.

비동기라는 개념이 일반적으로 필요한 이유는?

대부분은 서버와의 통신(네트워크 작업) 때문에

Serial vs Concurrent

Serial Queue:

보통 메인 thread에서) 분산처리 시킨 작업을 다른 한개의 thread에서 처리하는 queue

Concurrent Queue:

(보통 메인 thread에서) 분산처리 시킨 작업을 다른 여러개의 thread에서 처리하는 queue

분산처리 하려면 Concurrent Queue가 무조건 좋아 보이는 것 같은데

왜 serial queue가 필요할까?

Serial Queue와 Concurrent Queue를 사용하는 경우

직렬(Serial Queue)동시(Concurrent Queue)
순서가 중요한 작업을 처리할 때 사용각자 독립적이지만 유사한 여러개의 작업을 처리할 때 사용

동시작업이 필요한 경우:

컬랙션 뷰에서 독립적인 이미지 를 처리하지만 동시적으로 처리되어야 유저가 불편함을 느끼지 않고 스크롤을 통해 컨텐츠를 확인할 수 있게 된다.

DispatchQueue(GCD)의 종류와 특성


Dispatch Queue에는 여러가지 대기열이 있다.

대기열마다 특성이 다르기 때문에 작업의 특성, 원하는 일처리에 따라 대기열(Queue)의 특성에 맞게 작업을 보내면 된다.

원하는 Queue로 작업을 보내면 각 Queue가 각자 다른 thread를 생성하고 일을 처리하게 된다.

Main Queue

  • Main thread이자 Main Queue이기도 하다.
  • Serial(직렬) Queue
DispatchQueue.main.async {

}

우리가 기본적으로 실행하는 코드는 직렬 + 동기적으로 실행되어왔던 것이다.

우리가 평소에 print("print something") 라고 사용했던 메서드에는 아래와 같은 의미가 숨겨져 있던 것이다.

DispatchQueue.main.sync {
	print("print something")
}

Global Queue와 Qos의 이해

  • Default Setting: Concurrent
DispatchQueue.global().async {

}

GlobalQueue에는 총 6가지의 Queue가 존재한다.

Global Queue의 다양한 서비스 품질(Quality of Service)

Queue의 우선순위

iOS가 알아서 우선 순위에 있는 Queue의 작업이 더 중요한임을 인지하고 thread에 우선순위를 매겨 더 여러개의 thread를 배치하고 배터리를 더 집중해서 사용하도록 한다.

Private(Custom) Queue

  • Default setting: Serial

  • Concurrent setting available

  • Qos setting available

  • OS가 QoS를 알아서 추론한다

let queue - DispatchQueue(label: "customName")

let queue1 = DispatchQueue(label: "customName2", attributes: .concurrent)

Queue의 종류 정리

Operation에 대해


GCD와 Operation의 차이

  • 복잡한 일(커뮤니케이션의 양)
  • 데이터와 기능을 캡슐화한 객체
  • 취소 / 순서지정 / 일시중지 (상태추적)

언제 Operation이 필요할까?

그렇기에 유저가 빠르게 스크롤을 할 때 이미 지나간 셀의 이미지를 실제로 표시할 필요가 있을까에 대한 고민:

빠르게 스크롤을 해서 이미 지나간 이미지에 대한 다운로드 하는 작업을 취소해야하지 않을까?

Operation과 Operation의 개념을 통해 이런 부분을 해소할 수 있다.

Operation

  • Class화 된 작업

    • 단위(패키지) 작업, 클래스화한 작업

      • 기존에는 메서드와 같은 것들을 GCD를 통해서 작업을 클로저 안에 넣어서 보냈는데 작업 자체가 class화 되어서 해당 인스턴스를 이용하는 것이 Operation이다.

      • 기본적으로 동기적으로 설정 [Sync]

        • 그냥 사용하면 똑같이 1번 thread에서 동기적으로 사용이 된다
      • 인스턴스화해서 사용하고 → 작업 한번만 실행가능 (원하면 다시 객체를 생성해야 한다.)

    • 단위적인 작업을 클래스화 해서 기능을 향상 시키는 것

      • 취소
      • 순서지정(의존성)
      • 상태 체크(state machine)
      • KVO notifications
      • QoS 수준
      • completionBlock 제공
        • 작업을 끝마쳤을 때 어떤 것을 할 것인지 설정 가능
      • 재사용성 용이
  • Operation의 메서드 및 변수

    • start() 메서드
      • 오퍼레이션큐에 안넣고, 독립적으로 동작 가능하지만 일반적으로는 오퍼레이션 큐 사용
    • cancel 메서드
    • 상태 체크 위한 Bool값 변수
      • isReady
      • isExecuting
      • isCancelled
      • isFinished

Operation의 LIfeCycle

이런 상태 체크가 필요한 이유: Operation Queue가 궁극적으로 순서를 지정하거나 작업을 취소하기 위해서 위와 같은 bool 변수를 내장하고 있다.

Operation이란 애플에서 만든 추상적인 클래스다. 이를 개발자가 사용하기 위해서는 Operation을 상속 받아서 사용해야 한다.

개발자는

  • input 정의
  • output 정의
  • main()메서드 정의

해야 햔다.

class AnOperation: Operation {
  var inputImage: UIImage?
  var outputImage: UIImage?
  
  override func main() {
    // 정의해야 하는 메서드 내용
  }
  // 객체 생성 후 사용
  let op = AnOperation
}

Operation Queue

일반적으로 Operation을 담아서 사용하는 Operation Queue

  1. 몇 개의 thread를 사용할 것인지 구체적 설정 가능

    • Default == 1 (defaultMaxConcurrentOperationCount = -1)
    • maxConcurrentOperationCount = 1은 시리얼
    • maxConcurrentOperationCount = 2는 두 개의 thread 사용
  2. QoS 설정 가능

    • 기본 설정은 .background

    • Queue.qualityOfService = 원하는 품질 설정

    • Operation 자체의 품질을 설정하면, Queue의 품질이 승격될 수 있다

    • 기반이 되는 디스패치큐까지 설정이 가능하고 이는 가장 우선적으로 적용된다.

      • Queue.underlyingQueue = 원하는 품질
  3. Operation 추가 방법

    • 클로저, operation, operation 배열 추가 가능(waitUntilFinished 아규먼트 존재)

      (Operation을 Operation queue에 넣었을 때는 thread에 배치되었을 때 바로 isExecuting 상태가 된다.) start() 메서드 필요 없음

  4. Operation이 한번 실행되거나, 취소되면 Operation Queue를 떠난다.

  5. 동기적으로 기다리는 메서드 존재 waitUntilAllOperationsAreFinished()

    • Operation이 다 끝날 때 까지 기다려 주는 메서드
  6. 기능: 일시중지- 재개 가능

    • isSuspended - true / false 직접 설정 가능
      • True == 일시 중지
      • false == 재개
      • isSuspended = true 설정시 기존에 실행되던 Operation은 일단 계속 실행된다. 새로 추가된 Operation은 isSuspended = false될 때까지 스케줄링이 되지 않는다.
let operationQueue = OperationQueue()

operationQueue.addOperation(특정 Operation)
// 또는
operationQueue.addOperations([operation 배열], waitUntilFinished: true)
여러개의 operation을 전달하고 모든 operation이 끝날 때까지 기다린다.

!! Operation을 Operation Queue에 넣는 순간 비동기적으로 작동하게 된다.

Block Operation

*block == closure

안에 block을 내장하고 있는 오퍼레이션

  • 내부의 블락(클로저)들을 다른 쓰레드에서 일을 하도록 시킨다.
  • Dispatch Group과 유사하게 동작 [기능이 더 많은 디스패치 그룹이라고 봐도 된다.]
  • 각 블록은 디폴트 글로벌 큐에서 동시 동작하게 된다. Default == DispatchQueue.global()
  • 일반적인 기본 설정은 동기적으로 작동하게 된다.
    • Main thread를 블락하고 기다렸다가 해당 블록이 끝나면 다음 블록 실행
    • 비동기적으로 활용 가능

주된 사용

Operation을 많이 사용하는 앱에서, GCD와 같이 더 단순한 클로저가 필요한 경우에 사용

let blockOperation = BlockOperation()blockOperation.addExecutionBlock {// 추가적으로 더할 수 있는 작업}let blockOperation = BlockOperation {  result = 2 + 3}// 모든 block작업이 끝난 뒤 실행할 completion 정의blockOperationlcompletionBlock = {  print("===모든 출력 완료!===")}

[출처]:

iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션큐의 이해 대시보드 - 인프런 | 강의 (inflearn.com)

profile
james, the enthusiastic developer

0개의 댓글