이번 글은 스터디를 진행하면서 알게 된 내용입니다.
iOS 면접 질문에서 각자 원하는 주제를 설명하는 방식으로 진행되는데 제가 생각했을때 Concurrency에 대해서 이해하면 좋겠다고 생각해서 공부하게 되었습니다.
참고 강의 해당 유튜브 강의를 보면서 이해한 내용을 정리하였습니다.
Concurrency 즉 동시성은 뭘까요?
해당 내용을 이해하기 위해서는 동시성에 대한 정의를 먼저 공부해야됐습니다.
동시성 프로그래밍 이란 간단하게 많은 수의 코어를 효율적으로 활용하는데 도움을 주는 것 입니다.
여러개의 코어를 사용해서 처리 속도를 높이거나, 중요하지 않은 작업을 중요도가 낮은 스레드에서 실행시키는 등 성능적인 이점을 갖고 있습니다.
컴퓨터를 구매하게 되면 4코어 8쓰레드, 8코어 16쓰레드 등의 성능을 많이 보게 됩니다.
쓰레드는 간단하게 말씀드리면 일을하는 녀석 이라고 정의 할 수 있습니다.
예를 들어 쓰레드가 하나라면 하나의 쓰레드에서 여러가지 일을 수행하게 됩니다.
반면 쓰레드가 8개 라면 Thread1에서 처리하는 동안 Thread2, Thread3 ... ,Thread8 이 일은 다른 쓰레드에서 분산처리 할 수 있습니다.
Swift에서 Concurrency에 대해 검색하고 찾아보면 GCD와 비교를 많이하게 됩니다.
근데 초보자의 관점에서는 GCD가 뭔지 모르는 경우가 많아 GCD에 대해 먼저 정의하고 시작하고자 합니다.
GCD는 Swift Concurrency 가 등장하기 전 사용되는 동시성 프로그래밍 API 입니다.
GCD를 사용하면 async로 작업을 수행하고 나서 보통 탈출 클로저를 이용한 completion handler를 통해 해당 작업이 끝났을 때의 처리를 해주게 됩니다.
let serialQueue = DispatchQueue(label: "sungjin.serial")
let concurrentQueue = DispatchQueue(label: "sungjin.concurrent", attributes: .concurrent)
print(serialQueue) // Serial Dispatch Queue
print(concurrentQueue) // Concurrent Dispatch Queue
각 queue는 Sync와 Async 두 가지로 나눌 수 있습니다.
func forLoop(_ log: String) {
var result: Int = 0
for _ in 1...5 {
result += 1
}
print("\(log) = \(String(result))")
}
// Main queue: global이 실행된 이후 순차적으로 실행된다.
DispatchQueue.main.async {
forLoop("mainAsync")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
forLoop("mainAfterAsync")
}
순차적으로 실행 되는 것을 볼 수 있습니다.
// Global queue: 순서 상관없이 병렬 실행된다.
// 1과 2는 순서 상관없이 print 된다.
DispatchQueue.global().async {
forLoop("globalAsync1")
}
DispatchQueue.global().async {
forLoop("globalAsync2")
}
순서와 상관없이 실행되는 것을 볼 수 있다.
QOS 우선 순위
DispatchQueue.global().async {
// 다양한 작업 (UI 제외)
DispatchQueue.main.async {
// UI 작업
}
}
iOS에서 비동기(Async) 처리 방식은 해당 작업을 기다리지 않고 다음 작업을 진행하는 방식입니다.
이러한 방식은 작업을 분산 처리하여 성능을 높인다는 장점을 가지고 있지만,
특정 작업의 함수 결과물을 의존/사용하는 다른 작업이 존재할 경우 에러가 발생할 수 있습니다.
var resultName: String?
func myName(name: String) -> String?{ // n번 쓰레드에서 작업
DispatchQueue.global().async {
sleep(2)
resultName = name
}
return resultName
}
print(myName(name: "황성진"))
// 출력 결과 = nil
// myName() 함수의 작업이 끝나기 전에 print() 함수의 작업이 진행하기 때문에 nil(에러) 출력
var resultName: String?
func myName(name: String, completionHandler: @escaping (String?) -> Void){ // n번 쓰레드에서 작업
DispatchQueue.global().async {
sleep(2)
resultName = name
completionHandler(resultName)
}
}
myName(name: "황성진") { item in
print(item)
}
// 출력결과 : 황성진
작업 시간이 긴 함수들을 동기함수로 만들면 1번 쓰레드(Main Thread)에 과부하가 걸립니다.
이러한 이유로 작업 시간이 긴 함수를 내부에 비동기적 처리 하여 비동기로 동작하는 함수로 변형해야 합니다.
func myTask(programmingLanguage: String) -> String{
print("수학 숙제 시작")
sleep(2)
print("수학 숙제 종료")
print("\(programmingLanguage) 코딩 공부 시작")
sleep(2)
print("코딩 공부 종료")
print("영어 숙제 시작")
sleep(2)
print("영어 숙제 종료")
return "공부 종료"
}
myTask(programmingLanguage: "Swift")
func myTask(programmingLanguage: String) -> String{
print("수학 숙제 시작")
sleep(2)
print("수학 숙제 종료")
print("\(programmingLanguage) 코딩 공부 시작")
sleep(2)
print("코딩 공부 종료")
print("영어 숙제 시작")
sleep(2)
print("영어 숙제 종료")
return "공부 종료"
}
func asyncMyTask(programmingLanguage: String, completionHandler: @escaping (String) -> Void){
DispatchQueue.global().async {
let function = myTask(programmingLanguage: programmingLanguage)
completionHandler(function)
}
}
asyncMyTask(programmingLanguage: "Swift") { item in
print(item)
}
GCD(Grand Central Dispatch) 정리
Dispatch Queue(GCD) 사용 시 주의사항