컴퓨터로 영화를 틀어놓을 채로 문서도 보고 다운도 받고 동시에 여러가지 일을 처리할 수 있음
어떻게??
짤 하나로 정리되는 컴퓨터 용어 😎
CPU에서 실제로 일을 처리하는 부분
코어가 많다 == 일을 할 수 있는 녀석이 많다!
코어는 한 번에 한 가지 일만 처리할 수 있음 -> 근데 어떻게 동시에 여러가지 일을 할 수 있지 ⁉️
맥날 직원을 생각해보자
- 🤦사람에게 주문을 받고
- 🍔 주문 받은 햄버거를 가져오고
- 🍟 감자튀김을 튀기고
직원은 무려 3가지 일을 동시에 하고 있다. 하지만 정말 동시에일까?
직원의 팔이 여섯 개라 주문 받으면서 감자를 튀기면 동시라고 할 수 있갰지만 사실을 한 명의 직원이 여러 일을 조금씩 빠르게 돌아가면서 하는 것이다.
즉 한 번에 하는 것"처럼"
보이는 것이지 실제로는 일을 짧은 단위로 나누어서 번갈아가면서 처리
하는 것 --> 동시성 프로그래밍의 원리!
멀티 코어 역시 직원이 여러 명인 것이지 각 직원은 한 번에 하나씩만 처리한다.
논리적인 '코어'
논리적인 스레드
동시성 프로그래밍은 소프트웨어의 멀티 스레딩
을 이용한 기술❗️
여러 개의 코어(CPU)마다 작업을 하나씩 맡아 여러 작업을 동시에 처리하는 것
맥날 직원의 병렬 작업을 생각해보자
- 직원1 : 주문받기
- 직원2 : 햄버거 조리하기
- 직원3 : 감자튀김을 준비하기
직원들이 손님에게 햄버거 팔기
라는 일을 분담해 하나씩 작업을 처리하고 있어 위 세 가지 일은 정말 동시에 일어나고 있다.
여러 CPU가 하나의 일을 분담해서 처리하기 때믄에 혼자서 일을 처리하는 것보다 빠르게 할 수 있음
--> 물리적인 코어(CPU)가 여러 개일 때만 가능!
실제로 iOS 개발에서 병렬 프로그래밍을 직접 구현할 일은 없을 것이다.
그래도 개념은 알아두는게 좋다!
하나의 스레드에서만 순서대로 작업을 처리하는 것
하나의 CPU가 여러 작업을 동시에 (번갈아가면서) 처리하는 것
앞의 작업이 끝나기를 기다리는 것
현재 작업이 모두 끝나야 다음 작업으로 넘어갈 수 있다!
작업이 끝나는 것을 기다리지 않고 다음 작업을 실행하는 것
동시성 프로그래밍을 구현하려면 멀티 스레드 환경이어야 함. 많은 스레드들을 어떻게 관리할까?
Apple에서는 개발자가 코드로 동기/비동기 처리만 해주면 시스템이 스레드를 알아서 관리하게 해줌 == GCD
Grand Central Dispatch
: Apple이 제공하는 멀티 코어 환경과 멀티 스레드 환경에서 최적화된 프로그래밍을 도와주는 기술
Dispatch 프레임워크의 Dispatch Queue
클래스를 주로 사용
DispatchQueue
에 작업을 추가해주면 시스템이 알아서 스레드를 관리해서 작업을 처리
DispatchQueue
는 FIFO로 작업을 처리
Serial/Concurrent
// Serial Queue
DispatchQueue(label: "Serial")
DispatchQueue.main
// Concurrent Queue
DispatchQueue(label: "Concurrent", attributes: .concurrent)
DispatchQueue.global()
main
메모리에 늘 올라와 있는 기본 스레드
전역적으로 사용 가능
UI 작업은 메인 스레드에서만 작업할 수 있음
다른 스레드가 필요하다면 메인 스레드에서 필요한 만큼의 스레드가 파생됨
global
main 스레드가 아닌 작업을 처리하기 위해 만든 스레드
global() 메서드가 호출되면 메모리에 올라왔다가 작업이 끝나면 메모리에서 제거됨
sync/async
// sync
DispatchQueue.main.sync {}
DispatchQueue.global().sync {}
// async
DispatchQueue.main.async {}
DispatchQueue.global().async {}
DispatchQueue.main.async {
// 코드 1
}
DispatchQueue.main.async {
// 코드 2
}
코드1 -> 코드2
비동기니까 동시에 실행될거라 생각할 수 있어도 main 스레드 하나만 있기 때문에 순서대로 실행됨
DispatchQueue.main.async {
// 코드 1
}
// 코드 2
랜덤 실행(코드2
-> 코드1
)
둘 중의 어느 코드가 먼저 실행될지 모름
코드1
작업을 DispatchQueue에 넘긴 후 비동기이기 때문에 기다리지 않고 다음 코드를 실행해서 대개 코드2
가 먼저 실행됨
DispatchQueue.global().async {
// 코드 1
}
DispatchQueue.global().async {
// 코드 2
}
DispatchQueue.main.async {
// 코드 3
}
랜덤 실행
비동기로 DispatchQueue에 넘기기 때문에 각자 실행되어 어떤 것이 먼저 실행될지 모름 == 동시에 실행됨
여러 작업이 동시에 실행되려면 global
이면서 async(비동기)
여야 함
DispatchQueue.global().sync {
// 코드 1
}
DispatchQueue.global().sync {
// 코드 2
}
// 코드 3
코드1
-> 코드2
-> 코드3
global
이라 다른 스레드에서 실행되지만 동기이기 때문에 실행이 완료되는 것을 기다려서 순서대로 실행됨
// ❌ 에러
DispatchQueue.main.sync {
// 코드
}
main.sunc
을 호출하게 되면 에러가 발생해 동작하지 않는다 -> 교착상태(deadlock)에 빠짐
- main: 동기네? 끝날 때 기다려야지! = 멈춤
- sync 코드: (실행되야 하는데 내가 실행되는 main이 멈춰버렸네...) = 멈춤
- main: (뭐해..? 나 너 기다리는데..?)
- sync 코드: (너가 멈춰있는데 어떻게 실행해..?)
- main: ???
- sync 코드: ???
이게 바로 deadlock이다 😊
메인 스레드에서 매인의 동기 작업을 불러서 발생한 문제이므로 메인이 아닌 다른 스레드에서 부르면 가능하다!
DispatchQueue.global().async {
DispatchQueue.main.sync {
// 코드
}
}
DispatchWorkItem
를 이용하면 코드 블록을 캡슐화할 수 있다 -> 마치 클로저처럼 사용
let item = DispatchWorkItem {
// 실행할 코드
}
DispatchQueue.main.async(excute: yellow)
async
메서드를 원하는 시간에 호출할 수 있는 메서드
DispatchQueue.global().asyncAfter(deadline: .now() +5, execute: item)
5초 후에 uitem이라는 DispatchWorkItem
을 실행
deadline
대신에 wallDeadline
을 사용하면 시스템 시간을 기준으로 카운트
비동기 작업이 끝나는 시점을 기다릴 수 있는 메서드
비동기 작업이지만 의도적으로 기다려야 할 때 사용 -> sync
처럼 동작
DispatchQueue.global().asyncAndWait(execute: yellow)
커스텀 큐를 사용하기 위해 DispatchQueue
를 초기화할 수 있다.
convenience init(label: String,
qos: DispatchQoS = .unspecified,
attributes: DispatchQueue.Attributes = [],
autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit,
target: DispatchQueue? = nil)
DispatchQueue의
이름을 설정하는 파라미터
let myDispatchQueue = DispatchQueue(label: "myQueue")
디버깅 환경에서 큐를 추적하기 위해 필요한 String
DispathQoS
타입으로 우선순위를 정해주는 파라미터
QoS = Quality of Service
DispatchQueue
의 속성을 정해주는 파라미터
let item = DispatchWorkItem {
// 코드
}
let myDispatch = DispatchQueue(label: "myQueue", attributes: .initiallyInactive)
myDispatch.async(execute: item) // 코드 실행 안됨
myDispatch.active() // 코드 실행
acive()
를 호출해야 실행DispatchQueue
가 자동으로 객체를 해제하는 빈도를 결정하는 파라미터
코드를 실행할 큐를 target으로 설정
어떤 작업에 더 많은 스레드를 할당할지 결정하는 우선순위
결국 스레드는 시스템이 관리하기 때문에 QoS가 절대적인 우선순위 수치는 아님!
DispatchQueue
나 sync, async의 파라미터로 지정할 수 있음
func async(group: DispatchGroup? = nil, qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlasgs = [], execute work: @escaing () -> Void)
DispatchQueue
의 async 코드 블록을 묶어서 관리해주는 DispatchGroup
DispatchQoS
코드를 실행할 때 추가로 적용할 속성
CompletionHandler, completion -> 함수의 실행 순서를 보장받을 수 있는 클로저
비동기일 때는 메서드가 끝나는 시점을 알 수가 없음
-> CompletionHandler와 같은 클로저를 사용하면 비동기 메서드도 종료되는 시점을 추적 가능
비동기적으로 처리되는 작업을 그룹으로 묶어서 추적하는 기능 - async에서만 사용 가능
DispatchGroup
으로 async들을 묶어서 그룹의 작업이 끝나는 시점을 추적해서 동작을 수행하게 할 수 있다.
async 작업들이 꼭 같은 스레드나 큐에 있지 않아도 묶을 수 있다.
group
으로 지정하는 방법
enter
, leave
를 코드 앞뒤로 호출해서 group 지정let group = DispatchQueue()
// 1번 방법
DispatchQueue.main.async(group: group) {}
DispatchQueue.global().async(group: group) {}
// 2번 방법
group.enter()
DispatchQueue.main.async {}
DispatchQueue.global().async {}
group.leave()
DispatchGroup
의 작업이 끝나면 동작을 수행하기 위한 메서드
let group = DispatchGroup()
group.notify(queue: .main) {
// 작업이 끝난 후 실행할 코드
}
DispatchGroup
의 작업이 끝나는 것을 기다리는 메서드
별도의 코드를 실행하지 않고 기다리기만 한다.
let group = DispatchGroup()
group.wait()
print("모든 작업이 끝났습니다.")
wait
메서드에 파라미터로 timeout
을 설정해 기다리는 시간을 지정할 수 있다.
timeout
을 10으로 지정하면 10초만 기다리고 group 작업이 끝나지 않았어도 다음 코드를 실행한다.
let red = DispatchWorkItem {
// 코드 1
}
let blue = DispatchWorkItem {
// 코드 2
DispatchQueue.global().async(execute: yellow)
}
let group = DispathchGroup()
DispatchQueue.global().async(group: group, execute: red)
DispatchQueue.global().async(group: group, execute: blue)
group.wait()
print("모든 작업이 끝났습니다")
red, blue를 group으로 묶어 wait으로 기다리지만 blue 안에 있는 yellow는 끝나지 않아도 wait 이후로 넘어간다.
blue 안에 있는 yellow는 비동기 작업으로 이미 다른 스레드로 넘겼기 때문의 blue의 작업을 끝난 것이다!
만약 yellow도 같이 기다리고 싶다면 yellow도 group으로 묶어주면 된다.
하나의 값에 여러 스레드가 동시에 접근해서 발생하는 문제
ex) 같은 배열의 값을 꺼내고 지우는 동작을 하는 3개의 DispatchQueue.global().async
-> 하나의 값이 여러 번 꺼내질 수 있음
Race Condition이 발생하는 이유는 Swift 스레드가 Thread Safe하지 않기 때문이다
Thread Safe = 여러 스레드가 하나의 값에 동시에 접근하지 못하는 것