일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- view
- UIButton
- Firebase
- 개발자
- github
- appstore
- geofencing
- 웹뷰
- Code
- Swift
- MacOS
- SwiftUI
- Git
- darkmode
- window
- JPA
- Archive
- 한글
- IOS
- Realm
- Apple
- rxswift
- error
- iOS16
- Notification
- stack
- Xcode
- Session
- FLUTTER
- mac
- Today
- Total
EEYatHo 앱 깎는 이야기
Swift ) Concurrency, Thread, GCD - EEYatHo iOS 본문
Concurrency
CPU 칩은 발달을 거듭해 열과 같은 물리적 법칙 한계로 코어당 성능을 더이상 끌어올리기 힘들어졌고,
칩이 가지고 있는 코어를 늘리는 방향으로 발전하고 있다.
코어의 갯수는 계속해서 늘어날 것으로 예상된다.
이에 소프트웨어 개발자는 멀티코어(동시성) 프로그래밍을 어떻게 잘 할 수 있을지 고민해야한다.
애플 문서 링크
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가 설정되지 않았다는 뜻. 얘가 진짜 기본값
- 6가지 우선순위를 제공. concurrent 큐에서 Task 들을 병렬 처리할 때, 우선순위가 높은 Task 를 우선적으로 진행.
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 더함 )
- wait : Semaphore 에게 작업 시작을 요청
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
- 파일처리, 메모리, 타이머 등의 low 레벨 이벤트들을 감지하고, DispatchQueue 에 Task 를 주입
- 개발자가 커스텀하게 만들 수 도 있다
- 나중에 공부하기 https://developer.apple.com/documentation/dispatch/dispatchsource
Reference
'iOS, Swift > Swift Theory' 카테고리의 다른 글
Swift ) Swift 5.7 변경사항 - EEYatHo iOS (0) | 2023.02.06 |
---|---|
Swift ) async/await - EEYatHo iOS (0) | 2023.01.16 |
Swift ) Class vs Struct 성능 비교 - EEYatHo iOS (0) | 2023.01.06 |
Swift ) Dynamic Image - EEYatHo iOS (1) | 2022.10.15 |
Swift ) 고차함수 - EEYatHo iOS (2) | 2022.09.23 |