네이버 블로그/velog 에서 작성했던 내용들을 티스토리로 통합중입니다 :)
동시성 프로그래밍으로 프로그램 구성하기
메인스레드의 경우 UI관련 사용자와 직접 상호작용하는 작업을 처리하는데, 데이터변환, 이미지 처리를 메인스레드에서 전부 처리할 경우 약간의 버벅임 문제가 발생한다.
메인스레드의 과중화된 업무를 방지하기위해 나온것이 바로 Concurrency(동시성)이다.
동시성 프로그래밍은 여러개의 스레드를 사용함으로써 동시작업을 가능케한다. 하지만 단점도 있는데 바로 thread safety를 유지하기 어렵다는 것이다. 다른 스레드가 작업하는걸 순차적으로 가져와서 지키기가 어려워진다.
동시성 환경에서 thread safety를 유지하기 위한 노력으로 애플은 GCD 라이브러리를 만들어서 제공하고 있다.
Dispatch Queues and Run Loops
디스패치 큐란 작업에 대한 실행을 앱의 메인 쓰레드 혹은 백그라운드 쓰레드에서 직렬로 할지, 동시성으로 할지를 관리해주는 객체이다.
디스패치 큐에 work item을 차곡차곡 넣게 된다. (FIFO자료구조)
정리하자면 디스패치큐(대기열)에 작업을 넣어주면 다른 스레드에 dispatch하여 작업을 끝낸 후 메인스레드로 돌아온다. 이 방법을 통해 메인스레드의 과부하를 방지할 수 있다.
GCD를 적용하기 전에는 데이터플로우가 독립적으로 이루어졌을것이다. 이러한 독립적인 데이터 플로우를 GCD로 묶어줄 수 있다.
이렇게 묶어주면 기존의 독립적인 데이터 플로우가 Chaining이었다면 GCD는 데이터 관리를 Grouping work으로 관리하게된다.
스레드별 작업 그룹핑은 다음순서로 진행된다
먼저 디스패치그룹 객체를 만든다
→ 그룹핑할 큐 옵셔널 파라미터에 그룹 객체를 넣는다
→ 또다른 그룹핑할 큐 옵셔널 파라미터에 그룹객체를 넣어주어 묶는다
→ 마지막으로 그룹핑 작업들이 끝나면 메인스레드에 작업끝났다고 알려줄 notify를 넣는다
subsystem들을 한줄로 세워보자 .sync 메서드를 이용하면 간단하게 세울 수 있다. 이렇게 한줄로 세우면 데드락 현상을 방지할 수 있다.
Quality of Service
애플은 QoS 클래스를 통해 작업의 우선순위를 나눌 수 있는 기능을 제공한다 IO 스케쥴링 우선순위에 따라 개발자는 다양하게 작업의 우선순위를 부여하여 다른 스레드로 작업을 보낼 수 있다.
async 메서드의 QoS 파라미터를 통해 작업의 우선순위를 정해줄 수 있다
DispatchWorkItem
let myQueue = DispatchQueue(label: "com.nil.queue")
let workItem = DispatchWorkItem {
print("WorkItem task")
}
myQueue.async(execute: workItem)
print("========")
Dispatch work item은 Task를 캡슐화하는데 이용하는 클래스이다 item 객체로 묶어서 디스패치 큐에 넣을 수 있다. 그룹핑해서 사용도 가능하다
let workItem = DispatchWorkItem(qos: .userInitiated, flags: .assignCurrentContext) {
// Your task here
}
let queue = DispatchQueue(label: "com.example.queue", qos: .background)
queue.async(execute: workItem)
디폴트값으로 async는 실행될 때의 qos를 캡쳐한다 하지만 DispatchworkItem은 코드 작성 시점에 깃발을 꽂아서 qos를 지정해줄 수 있다 따라서 위의 queue 경우 .userInitiated로 큐가 실행된다.
.wait 메서드를 통해 꼭 결과물이 필요한 아이템(작업)을 기다려줄 수 있다
DispatchWorkItem을 기다려주는 기능을 통해 정보의 ownership을 부여할 수 있다
기존의 세마포와 그룹과의 결정적인 차이는 바로 이러한 ownership을 가질 수 있다는 것이다
Shared State Synchronization
전역변수는 atomic 하게 초기화된다.
하지만 클래스 프로퍼티는 아토믹하지 않다
레이지 프로퍼티 또한 아토믹하게 초기화되지 않는다 (race condition 이슈)
class BankAccount {
var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) {
if balance >= amount {
balance -= amount
} else {
print("Insufficient funds!")
}
}
}
BankAccount 클래스는 Atomic하지만 balance는 그렇지않다. 데이터경쟁이 일어날 수 있기 때문이다
Atomic 하지 않다면 어떤 상황이 발생할 수 있을까? 다음과 같은 상황을 가정해보자
ThreadA는 800달러를 인출한다
ThreadB는 500달러를 인출한다
스레드A와 B는 동시에 인출전 1000달러인 것을 확인한 후 안심하며 돈을 뺀다
하지만 결과적으로는 -300달러로 되어버린다
이렇게 다수의 스레드가 접근해서 get/set을 해버릴 가능성이 있을 때 atomic 하지 않다고 한다
class ThreadSafeBankAccount {
private var _balance: Double = 0.0
private let balanceQueue = DispatchQueue(label: "balanceQueue")
var balance: Double {
return balanceQueue.sync { _balance }
}
func deposit(amount: Double) {
balanceQueue.sync {
_balance += amount
}
}
func withdraw(amount: Double) {
balanceQueue.sync {
_balance -= amount
}
}
}
위와 같은 문제점을 .sync 메서드를 통해 동기화함으로서 방지할 수 있다.
스레드들이 하나의 balanceQueue를 통해 접근함으로써 순서대로 데이터를 get/set 을 진행한다
class DatabaseManager {
lazy var connection: DatabaseConnection = {
print("Establishing connection...")
return DatabaseConnection()
}()
}
레이지 프로퍼티 또한 아토믹하지 않다 스레드A와 스레드B가 커넥션에 접근할 때 프린트가 두번찍히는 것을 볼 수 있다 즉 같은 프로퍼티가 중복되어 돌아가고 있다는 뜻이다
Use GCD for Synchronization
위와 같이 새로들어오는 상태를 .sync에 넣어줌으로써 데이터 경쟁을 방지할 수 있다.
작업아이템들을 어느 큐에 들어가게하고 어느 큐에는 절대 안들어가는지 미리 위와 같은 코드로 정해줄 수 있다..
onQueue: 이 큐로 들어와 ^^
notOnQueue: 여긴 얼씬도 하지마 ^^
동시성 세계에서 객체의 생명주기
→ 먼저 Setup을 통해 object를 Creating 한다
→ 만들어진 object를 activate 한다
→ 동작중인 stateMachine을 invalidate 해준다.
→ 스레드 메모리에서 해제한다
'Flutter' 카테고리의 다른 글
Swift 에서 Key-Value Observing 이란? (0) | 2024.06.24 |
---|---|
위젯에 Key 를 사용해야하는 이유 (0) | 2024.06.12 |
Flutter 화면전환에서 routes 와 onGenerateRoute 의 차이 (0) | 2024.06.04 |
데이터 바인딩과 Reactive X (0) | 2024.06.03 |
Hot Observable & Cold Observable (0) | 2024.06.01 |