본문 바로가기
iOS/System

Queues & Threads - Concurrency by Tutorials

by 탄이. 2020. 6. 14.

Chapter 3: Queues & Threads

Concurrency by Tutorials
In this part of the book, you're going to learn about the basics of Concurrency. You're going to learn what it is, what kind of problems it solves, and why would you even use it? Further, you will learn the basic pieces of which Concurrency comprises in Cocoa development: Grand Central Dispatch and Operations.
https://store.raywenderlich.com/products/concurrency-by-tutorials

Threads

  • 멀티쓰레드의 이점
    1. 빠른 실행
    1. 민감성: 사용자가 볼 수 있는 메인 UI쓰레드에서 모든 작업을 실행하면 앱이 느려지거나 멈추게 됩니다.
    1. 자원 소비 최적화
  • Apple은 스레드 관리에 필요한 API를 제공하지만 직접 관리하려고 하면 실제로 성능을 향상시키는 것이 아니라 성능을 저하시킬 수 있습니다.
    • OS는 많은 통계를 추적하여 언제 스레드를 할당 또는 파기해야 하는지 알 수 있습니다.

Dispatch queues

  • 스레드로 작업하는 방법은 DispatchQueue를 생성하는 것입니다.
  • 대기열을 만들면 OS는 잠재적으로 하나 이상의 스레드를 만들어 대기열에 할당합니다.
  • 기존 스레드가 사용 가능한 경우 재사용 할 수 있습니다. 그렇지 않은 경우 OS는 필요에 따라 생성합니다.

The main queue

  • 이전 장의 내용을 복기해보면 직렬 또는 동시의 두 가지 유형의 디스패치 큐가 있었습니다.
  • 다음 코드와 같이 작성하면 기본 초기화 프로그램은 동시 대기열을 생성합니다.
    let label = "com.raywenderlich.mycoolapp.networking" 
    let queue = DispatchQueue(label: label, attributes: .concurrent)

Quality of service

동시 대기열이 필요하지만 직접 관리하고 싶지 않은 경우 DispatchQueue에서 전역 클래스 메소드를 사용하여 사전 정의 된 전역 대기열 중 하나를 가져올 수 있습니다.

  • .userInteractive
    • 사용자가 직접 상호 작용하는 작업에 권장됩니다.
    • UI 업데이트 계산, 애니메이션 또는 UI를 신속하고 신속하게 유지하는 데 필요한 모든 것.
  • .userInitiated
    • 사용자가 UI에서 작업을 즉시 시작할 때 사용해야 하지만 비동기 적으로 수행 할 수 있습니다.
    • 예를 들어, 문서를 열거 나 로컬 데이터베이스에서 읽어야 할 수 있습니다.
    • 사용자가 버튼을 클릭 한 경우 원하는 대기열 일 수 있습니다.
    • 이 대기열에서 수행 된 작업을 완료하는 데 몇 초 정도 걸립니다.
  • .utility
    • 일반적으로 장기 실행 계산, I / O, 네트워킹 또는 연속 데이터 피드와 같은 진행률 표시기가 포함 된 작업에 .utility 발송 대기열을 사용하려고합니다.
    • 시스템은 에너지 효율성과 응답 성과 성능의 균형을 유지하려고합니다.
    • 이 대기열에서 작업은 몇 초에서 몇 분 정도 걸릴 수 있습니다.
  • .background
    • 사용자가 직접 알지 못하는 작업의 경우 .background 대기열을 사용해야합니다.
    • 사용자 상호 작용이 필요하지 않으며 시간에 민감하지 않습니다.
    • 프리 페치, 데이터베이스 유지 보수, 원격 서버 동기화 및 백업 수행이 모두 좋은 예입니다.
    • OS는 속도 대신 에너지 효율성에 중점을 둘 것입니다.
    • 이 대기열을 몇 분 이상 걸리는 데 상당한 시간이 걸리는 작업에 사용하려고 합니다.
  • .default and .unspecified
    • 가능한 두 가지 다른 선택 사항이 있지만 명시 적으로 사용해서는 안됩니다.
    • .default 옵션은 .userInitiated.utility 사이에 있으며 qos 인수의 기본값입니다.
    • 직접 사용하기위한 것이 아닙니다.
    • 두 번째 옵션은 .unspecified이며 스레드를 서비스 품질에서 제외시킬 수있는 레거시 API를 지원하기 위해 존재합니다.
    • 그것들이 존재한다는 것을 아는 것이 좋지만, 당신이 그것들을 사용하고 있다면 거의 확실하게 무언가 잘못하고 있는 것입니다.
💡
global 큐는 항상 동시적이고 first-in, first-out입니다.

inferring QoS

  • 자체 동시 디스패치 큐를 작성하는 경우 초기화 프로그램을 통해 QoS가 무엇인지 시스템에 알려줄 수 있습니다.
    let queue = DispatchQueue(label: label, 
    													qos: .userInitiated, 
    													attributes: .concurrent)
  • 현재 컨텍스트가 기본 스레드 인 경우 유추 된 QoS는 .userInitiated입니다. 직접 QoS를 지정할 수 있지만, QoS가 더 높은 작업을 추가하자마자 대기열의 QoS 서비스가 일치하도록 늘어납니다.

Adding task to queues

  • 디스패치 큐는 작업을 큐에 추가하기 위해 동기 및 비동기 메서드를 모두 제공합니다.
  • 앱이 시작될 때, 작업별로 "단순히 모든 코드 블록을 실행해야합니다" 라는 의미입니다.
  • 또는 예를 들어, 앱을 업데이트하기 위해 서버에 연락해야 할 수도 있습니다.
DispatchQueue.global(qos: .utility).async { [weak self] in 
	guard let self = self else { return }

	// Perform your work here // ...

	// Switch back to the main queue to 
	// update your UI 
	DispatchQueue.main.async { 
		self.textLabel.text = "New articles available!" 
	}
}
  • 위의 코드 샘플에서 가져가야 할 두 가지 핵심 사항이 있습니다.
    1. 첫째, DispatchQueue에는 클로저 규칙을 무효화하는 특별한 것이 없습니다.
      • 클로저의 캡처 된 변수 (예 : self)를 활용하려는 경우 여전히 올바르게 처리하고 있는지 확인해야합니다.
      • GCD 비동기 클로저에서 자기를 강력하게 캡처하면 전체 클로저가 완료되면 할당이 해제되기 때문에 참조주기 (예 : retain cycle)가 발생하지 않지만 자체 수명이 연장됩니다.
      • 예를 들어, 그 동안 해제 된 뷰 컨트롤러에서 네트워크 요청을해도 클로저는 계속 호출됩니다.
      • 뷰 컨트롤러를 약하게 캡처하면 nil이됩니다.
      • 그러나 강력하게 캡처하면 클로저가 작업을 완료 할 때까지 뷰 컨트롤러가 활성 상태로 유지됩니다.
      • 이를 염두에두고 필요에 따라 약하거나 강하게 캡처하십시오.
    1. 둘째, UI에 대한 업데이트가 백그라운드 대기열에서 디스패치 내의 메인 대기열로 어떻게 디스패치되는지 확인하십시오.
      • 비동기식 호출을 그 내부의 다른 비동기식 호출에게 중첩시키는 것은 괜찮을 뿐 아니라 매우 일반적입니다.
    💡
    기본 대기열 이외의 대기열에서는 UI 업데이트를 수행해서는 안됩니다. API 콜백이 사용하는 대기열을 문서화하지 않은 경우 기본 대기열로 전달하십시오!
    • 작업을 디스패치 큐에 동기적으로 제출할 때는 각별히 주의하십시오.
      • 비동기 메서드 대신 동기 메서드를 호출하는 경우 실제로 수행해야 하는 작업인지 한두 번 생각하십시오.
      • 현재 대기열을 차단하는 작업을 현재 대기열에 동기식으로 제출하고 작업이 현재 대기열의 리소스에 액세스하려고 하면 앱이 교착 상태가 발생합니다.
      • 마찬가지로 메인 대기열에서 동기를 호출하면 UI를 업데이트하는 스레드가 차단되고 앱이 정지 된 것처럼 보입니다.
    💡
    메인 스레드에서 동기를 호출하지 마십시오. 그것은 당신의 메인 스레드를 차단하고 잠재적으로 교착 상태를 일으킬 수 있습니다.

Image loading example

Using a global queue

private func downloadWithGlobalQueue(at indexPath: IndexPath) { 
	DispatchQueue.global(qos: .utility).async { [weak self] in 
		guard let self = self else { return }

		let url = self.urls[indexPath.item]

		guard let data = try? Data(contentsOf: url), 
			let image = UIImage(data: data) else { return }

		DispatchQueue.main.async { 
			if let cell = self.collectionView
				.cellForItem(at: indexPath) as? PhotoCell {
				
				cell.display(image: image)
			} 
		}
	} 
}
// inside method ..cellForRowAt..

cell.display(image: nil) 
downloadWithGlobalQueue(at: indexPath)

Using built-in methods

private func downloadWithUrlSession(at indexPath: IndexPath) { 
	URLSession.shared.dataTask(with: urls[indexPath.item]) { 
		[weak self] data, response, error in

		guard let self = self, 
			let data = data, 
			let image = UIImage(data: data) 
			else { return }

		DispatchQueue.main.async { 
			if let cell = self.collectionView
				.cellForItem(at: indexPath) as? PhotoCell { 
				
				cell.display(image: image) 
			} 
		} 
	}.resume()
}

DispatchWorkItem

  • 익명 클로저를 전달하는 것 외에도 작업을 DispatchQueue에 제출하는 다른 방법이 있습니다.
  • DispatchWorkItem은 큐에 제출하려는 코드를 보유 할 실제 객체를 제공하는 클래스입니다.
DispatchQueue(label: "xyz").async {
	print("The block of code ran!")
}

let workItem = DispatchWorkItem {
	print("The block of code ran!")
}

DispatchQueue(label: "xyz").async(execute: workItem)

Canceling a work item

  • 명시적 DispatchWorkItem을 사용하려는 이유 중 하나는 실행 전이나 실행 중에 작업을 취소해야 하기 때문입니다.
  • work item에서 cancel()을 호출하면 다음 두 조치 중 하나가 수행됩니다.
    1. 작업이 대기열에서 아직 시작되지 않은 경우 제거됩니다.
    1. 작업이 현재 실행중인 경우 isCancelled 속성이 true로 설정됩니다.
  • 코드에서 isCancelled 속성을 주기적으로 확인하고 가능한 경우 작업을 취소하기 위해 적절한 조치를 취해야합니다.

Poor man's dependecies

  • DispatchWorkItem 클래스는 또한 현재 작업 항목이 완료된 후 실행되어야 하는 다른 DispatchWorkItem을 식별하는 데 사용할 수있는 notify(queue:execute:) 메소드를 제공합니다.
let backgroundWorkItem = DispatchWorkItem { }
let updateUIWorkItem = DispatchWorkItem { }

backgroundWorkItem.notify(queue: DispatchQueue.main,
													execute: updateWorkItem)
DispatchQueue(label: "xyz").async(execute: backgroundWorkItem)

 

댓글