일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- GIT
- swift
- SRP
- gitflow
- 오픈채팅방
- 등굣길
- SeSAC
- MainScheduler.asyncInstance
- RaceCondition
- rxswift
- SwiftUI
- Realm
- 명품cppProgramming c++
- MainScheduler
- DependencyInjection
- DiffableDataSource
- MethodSwilzzling
- cleanarchitecture
- MainScheduler.Instance
- data_structure
- 코테
- GCD
- combine
- leetcode
- 청년취업사관학교
- IOS
- CoreBluetooth
- DispatchQueue
- 프로그래머스
- DynamicMemberLookup
- Today
- Total
Do.
Swift, Concurrency 본문
Concurrency(동시성)
동시성 프로그래밍이라고도 한다. Concurrency
는 여러 작업을 나누어서 처리하는 것으로, 우리가 사용하는 아이폰이 노래도 재생하면서, 유저의 입력에 따라 이메일도 작성할 수 있고, 중간에 전화도 받을 수 있는 것이 이 동시성이다.
만약 노래를 재생하는 동안은 화면을 터치할 수 없고, 인터넷 검색도 불가능하다면, 굉장히 불편할 것이다.
코드로 실제로 어떻게 되는지 보자
//1
func calculatePrimes() {
for number in 0...1_000_000 {
let isPrimeNumber = isPrime(number: number)
print("\(number) is prime: \(isPrimeNumber)")
}
}
//2
func isPrime(number: Int) -> Bool {
if number <= 1 {
return false
}
if number <= 3 {
return true
}
var i = 2
while i * i <= number {
if number % i == 0 {
return false
}
i = i + 2
}
return true
}
calculatePrimes()
는 1에서 1백만 까지의 자연수 중에 소수 를 구하는 메서드로, 해본 사람은 알겠지만 꽤 오랜 시간이 걸리는 작업이다.
var body: some View {
VStack {
Spacer()
DatePicker(selection: .constant(Date())) {
Text("Date")
}
.datePickerStyle(.wheel)
.labelsHidden()
Button {
calculatePrimes()
} label: {
Text("Calculate Primes")
}
Spacer()
}
화면에는 DatePicker UI가 있고, 버튼을 누르면 소수를 구하기 시작한다.
소수 구하기 버튼을 누르면 해당 계산을 하느라 DatePicker의 UI가 동작하지 않는다.
그 이유는 DatePicker의 UserInteraction과 calculatePrimes()
를 둘다 메인 쓰레드에서 수행하고 있기 때문이다. calculatePrimes()
가 쓰레드를 점유하고 있어서 다른 메인 쓰레드에서 작업해야 하는 User Interaction이 동작하지 않는 것이다.
Thread/Multithread
멀티쓰레드 라는 얘기 많이 들어보았는데, 그럼 쓰레드는 뭘까,
컴퓨팅에서 쓰레드는 OS 스케줄러에 의해 독립적으로 관리될 수 있는 프로그래밍된 명령의 가장 작은 시퀀스다.
요즘은 CPU는 다수의 코어와 그 이상의 쓰레드를 가지고 있어, 물리적으로 작업을 동시에 처리할 수 있다. 이게 멀티 쓰레딩이다.
이 부분이 Parallelism
과 차이점이다. 병렬프로그래밍이라고 하는 Parallelism
는 작업의 단위가 스레드가 아니라, CPU가 된다. 앞서 caculatePrimes()
메서드를 하나의 스레드에서 점유해서 계산하지 않고, 분할해서 계산해서 작업의 속도를 높이는 것이 Parallelism
이고Concurrency
의 핵심은 스레드이다.
Context Swiching
Concurrency
의 또 하나의 핵심은 Context Switching으로, 하나의 코어는 Time Slicing이라는 방법을 통해 Concurrency하게 움직인다. 쉽게 이해해보면, 내가(아이폰) 커피를 만드는데 이 커피를 만드는 동작안에는 원두를 분쇄하고, 물을 끓이고, 컵을 준비하는 일련의 동작이고, 이를 아주 빠르게 한다, 다른 사람(사용자) 입장에서 보면 거의 동시에 일어나는 것 처럼 보인다.
Use Concurrency
그래서 다시 맨 처음 어플리케이션으로 돌아와서, iOS는 기본적으로 멀티쓰레드이다. 소수를 구하는 것과 동시에 UI작업을 처리할 수 있는 것이 당연한 것이다. 그런데 실제로는 계산을 하느라 UI작업을 하지 못했다. 우리는 소수를 구하는 작업을 다른 쓰레드에서 하도록 명시할 필요가 있다.
애플은 고맙게도? 쓰레드를 편리하게 사용할 수 있는 프레임워크를 제공한다. 굉장히 Low하게 보면 NSThread와 더 내려가서 Unix POSIX 쓰레드를 통해서 이 쓰레드를 사용할 수 있지만 우리에게는 Grand Central Dispatch(GCD)가 있다. GCD 또한 Concurrency 작업을 위한 프레임워크로 꽤 LowLevel 측면으로 디자인 되어있다.
또다른 옵션은 Operation Queue로 GCD위에 만들어져 있고, 더 쉽고 간결한 코드를 제공한다.
그 다음은 2019년에 나온 Combine으로 이는 백그라운드에서 작업을 선언형으로 관리한다. 오퍼레이터를 통해 스레드 간의 쉬운 전환이 가능하다.
또! 그다음은 Swift Concurrency가 있다.
GCD
GCD는 스레드 위에 구축되며, 공유 스레드 풀을 관리한다. 이를 사용해서 DispatchQueue에 코드 블록을 처리하고 GCD는 이를 실행할 쓰레드를 결정하게 된다.
Queue
Queue는 FIFO구조로 먼저 들어온 작업을 먼저 내보낸다는 특징이 있다. GCD는 같은 방식으로 작업 순서를 보장한다. 이때 Queue는 Seiral이거나 Concurrent일 수 있다.
이처럼 Serial은 선형 시간동안 하나의 작업만 실행된다. Task1이 끝나면 Task2가 실행되는 식이다. 쓰레드를 사용해서 이를 Concurrent하게 할 수 있다.
Concurrent는 Task순서대로 일단 작업을 할당하긴 했지만 Serial과는 다르게 Task2보다 Task3과 Task4가 훨씬 더 빨리 끝나서 작업물을 반환한다. 그래서 작업 순서를 보장할 수 없다.
커피를 다 볶은 다음에 컵에 넣고 물을 부어야 하는데, 물이 먼저 끓었다고 물부터 냅다 부어버린 것
Operation Queue
GCD를 사용하기 위해서는 Operation을 상속해서 만들 수 있다.
class CaculatePrimeOperation: Operation {
override func main() {
for number in 0...1_000_000 {
let isPrimeNumber = isPrime(number: number)
print("\(number) is prime: \(isPrimeNumber)")
}
}
func isPrime(number: Int) -> Bool {
if number <= 1 {
return false
}
if number <= 3 {
return true
}
var i = 2
while i * i <= number {
if number % i == 0 {
return false
}
i = i + 2
}
return true
}
}
main()
에서 앞에서 작성한 소수 구하기 메서드를 이식한다.
let operation = CaculatePrimeOperation()
func calculatePrimes() {
let queue = OperationQueue()
queue.addOperation(operation)
}
그런 다음 해당 클래스의 인스턴스를 만든 뒤, OperationQueue의 인스턴스에addOperation
으로 넘겨준다.
func calculatePrimes() {
let operation = CalculatePrimeOperation()
let queue = OperationQueue()
queue.addOperation(operation)
}
사실 꼭 Operation을 상속하는 클래스를 만들 필요는 없고 addOperation이 클로져를 제공하기 때문에 메서드를 해당 클로져 안에 작성해주면 된다.
OperationQueue
는 인스턴스를 만들 때 자동으로 남는 쓰레드를 할당해 준다. 만약 메인 쓰레드를 사용하라고 명시적으로 알리고 싶다면
let mainQueue = OpearationQueue.main
으로 쓸 수 있다.
OperationQueue
말고도 더 쉽고 간편한 방법은 DispatchQueue
가 있다
DispatchQueue
DispatchQueue.global(qos: .userInitiated).async {
for number in 0...1_000_000 {
let isPrimeNumber = number.isPrime
print("\(number) is prime: \(isPrimeNumber)")
}
}
쓰레드를 global로 보낼 수 있다. qos설정은 애플 문서에서 자세히 확인할 수 있다.
https://developer.apple.com/documentation/dispatch/dispatchqos/qosclass
그래서 이제 앞에서 소수를 구하느라 반응하지 않던 UI를 구조할 수 있게 되었다.
Swift Concurrency
이 작업을 Swift Concurrency를 이용하면 코드 한줄? 추가하는 것으로 가능하다.
func calculatePrimes() {
doneLabel = "Calculating!"
Task {
for number in 0...1_000_000 {
let isPrimeNumber = number.isPrime
print("\(number) is prime: \(isPrimeNumber)")
}
}
doneLabel = "Done!"
}
Refereces
'iOS' 카테고리의 다른 글
Firebase Auth 전화번호 회원가입 (0) | 2022.02.09 |
---|---|
iOS 15.0 UIButton (0) | 2022.02.09 |
Swift - Function Notation (0) | 2022.02.09 |
Swift - Any vs AnyObject (0) | 2022.02.09 |
Swift - associatedtype in protocols (0) | 2022.02.09 |