[Swift] GCD, Async/Sync 알아보기 비동기(Asynchronous)란 말과 Concurrency(동시성)란 말이 같은 말인가?
- -
Concurrency(동시성) 프로그래밍
왜 동시성 프로그래밍인가 ??
우리가 은행을 갔다고 해보자. 아래와 같이 은행원1에게만 고객이 줄을 선다고 하면
대기중인 고객은 자신의 앞에 있는 고객이 끝나기 전까지는 계속해서 기다려야 한다.
하지만 옆에 있는 은행원들 에게 고객을 분산시켜주면 아주 빠르게 고객들의 업무를 볼 수 있다.
즉 은행원1에게 쌓인 일을 분산시킨다면 고객들이 아무리 은행에 많이 와도 아주 빠르게 업무를 해결할 수 있는것이다.
즉 우리는 지금까지 작성한 코드들은 위와 같이 은행원1(메인스레드) 에게만 일을 맡겼던 것이다.
func Task1() {
...
}
func Task2() {
...
}
func Task3() {
...
}
Task1()
Task2()
Task3()
Task를 어떻게 다른 Thread에게 분산 시킬 수 있을까 ?
iOS에서는 Task를 Queue에 보내기만 하면 된다
다양한 예시
let queue = DispatchQueue.global()
//클로저를 해당 큐에 비동기적으로 보낸다
queue.async {
// 클로저
}
//클로저를 해당 큐에 동기적으로 보낸다
queue.sync {
// queue
}
// 당연히 클로저 내부는 순차적으로 실행
DispatchQueue.global().async {
... // 클로저 내부가 작업의 한 단위가 됨 Task1
}
DispatchQueue.global().async {
... // 클로저 내부가 작업의 한 단위가 됨 Task2
}
DispatchQueue.global().async {
... // 클로저 내부가 작업의 한 단위가 됨 Task3
}
GCD(Grand Central Dispatch) / Operation
iOS에서는 Queue가 두가지가 있는데 DispatchQueue(GCD)와 OperationQueue라는 대기 행렬이 있다.
직접적으로 스레드를 관리하지 않고 큐에 작업을 넣으면 시스템에서 알아서 스레드들을 관리한다
GCD
- 간단한 일과 함수를 사용하는 작업. 즉 메소드 위주
Operation
- GCD를 기반으로 발전된 큐이다
- 복잡한일과 데이터와 기능을 캡슐화한 객체
- 취소 / 순서지정 / 일시중지 사용 가능
동기 (Synchronous) VS 비동기(Asynchronous)
우리가 컵라면을 끓여 먹는다고 했을 때 뜨거운 물을 붓고 3분동안 기다려야 한다.
라면이 익기를 3분동안 기다리는 동안 우리는 김치를 꺼내거나 젓가락을 세팅하는 등 다른 일들을 할 수 있다.
이것이 비동기라고 생각하면 이해하기 쉽다.
일을 시키지만 그동안 기다리지 않는 것이 핵심이다.
반대로 동기라면 라면에 물을 붓고 라면이 익을 때 까지 아무것도 하지 못하고
3분이 지난 다음에서야 김치를 꺼내고 그 다음 젓가락을 꺼낼 수 있는 것이다
동기 (Synchronous)
작업을 시작시키고 작업이 끝날 때 까지 기다린다
Task1, Task2, Task3가 있고 메인스레드가 Task1를 스레드1로 보낸다고 하였을 때 동기이기 때문에
Task1은 스레드1에서 실행되고 메인스레드는 아무것도 하지 않고 있지만 메인 스레드는 스레드1이 Task1을 다 끝내는 10초동안 대기하고 있어야 한다. 결국 메인스레드에서 Task1을 실행하는것과 다르지 않게 된다.
비동기(Asynchronous)
작업을 시작시키고 작업이 끝날 때 까지 기다리지 않는다
비동기이기 때문에 메인스레드가 Task1을 스레드1로 보내도 Task1이 끝나는 것을 기다리지 않고 곧바로 다음 Task2를 실행한다.
코드 예시
이제 위에서 작성했던 코드의 의미를 알 수 있다.
// 작업을 큐에 보내고 작업이 끝날때 까지 기다리지 않는다
DispatchQueue.global().async {
}
// 작업을 큐에 보내고 작업이 끝날때 까지 기다린다
DispatchQueue.global().sync {
}
비동기일 때
func task1() {
DispatchQueue.global().async {
print("task1 시작")
... 작업 ...
print("task1 종료")
}
}
func task2() {
DispatchQueue.global().async {
print("task2 시작")
... 작업 ...
print("task2 종료")
}
}
func task3() {
DispatchQueue.global().async {
print("task3 시작")
... 작업 ...
print("task3 종료")
}
}
print("비동기 시작")
task1()
task2()
task3()
print("비동기 끝")
// 비동기 시작
// 비동기 끝
// task1 시작
// task3 시작
// task2 시작
// task2 종료
// task3 종료
// task1 종료
task1,2,3이 먼저 시작되었지만 비동기이기 떄문에 비동기 끝이 먼저 출력된다.
task의 시작종료는 그때그때 먼저 시작되는 task가 달라진다.
비동기 처리는 우리가 시작시키는것이 아니라 운영체제가 알아서 해주기 때문에 순서가 변경될 수 있다
동기일 때
func task1() {
DispatchQueue.global().sync {
print("task1 시작")
... 작업 ...
print("task1 종료")
}
}
func task2() {
DispatchQueue.global().sync {
print("task2 시작")
... 작업 ...
print("task2 종료")
}
}
func task3() {
DispatchQueue.global().sync {
print("task3 시작")
... 작업 ...
print("task3 종료")
}
}
print("비동기 시작")
task1()
task2()
task3()
print("비동기 끝")
// 비동기 시작
// task1 시작
// task1 종료
// task2 시작
// task2 종료
// task3 시작
// task3 종료
// 비동기 끝
동기이기 때문에 앞의 작업이 끝나야만 그 다음 작업을 시작할 수 있다.
직렬(Serial) VS 동시(Concurrent)
큐의 특성중 하나 serial큐의 경우 하나의 스레드를 사용하고 concurrent큐는 여러개의 스레드를 사용한다.
직렬(Serial)
하나의 스레드를 사용한다. 한개의 스레드를 사용하지만 어떤 스레드를 사용할지는 알 수 없고 운영체제가 알아서 스레드를 하나 사용한다
분산시킨 작업을 다른 한개의 스레드에서 처리하는 큐
동시(Concurrent)
여러개의 스레드를 사용한다. 몇개의 스레드를 사용하여 작업을 분배할지는 운영체제가 알아서 정한다.
2개의 스레드를 사용할 수 도 5개의 스레드를 사용할 수 도 있다.
분산시킨 작업을 다른 여러개의 스레드에서 처리하는 큐
Concurrent 큐가 분산 처리에 더 좋아보이는데 Serial 큐가 필요한 이유
직렬큐는 순서가 중요한 작업을 할 때 필요하기 때문이다.
예를 들어 이미지를 가져온 뒤 그 이미지를 전달 받아 테이블뷰 셀에 표시한다고 했을 때
이미지를 먼저 받아야 이미지를 표시할 수 있기 때문에
이미지를 다운받는 작업이 실행되고 난 후 이미지를 표시할 작업을 해야하기 때문이다.
// 동시 큐라면 작업1과 작업2가 동시에 실행될수도 있다.
// 하지만 직렬큐라면 작업1이 실행된 뒤 작업2가 실행된다.
var 이미지: UIImage
func 작업1() {
이미지 = 이미지 다운로드
}
func 작업2() {
테이블뷰 셀의 이미지 = 이미지
}
DispatchQueue.global().async {
작업1()
}
DispatchQueue.global().async {
작업2()
}
반대로 독립적이나 유사한 여러개의 작업을 하는 경우엔 동시큐가 더 좋다.
예를 들어 테이블 뷰에서 각 셀에 이미지를 다운받아 표시해야 한다면 동시큐로 진행한다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
DispatchQueue.global().async {
셀.이미지 = 이미지 다운로드
}
}
큐의 종류
SerialQueue.sync
- 메인 스레드의 작업흐름이 큐에 넘긴 작업이 끝날때까지 멈춰(sync)있다
- 넘겨진 작업은 큐에 먼저 담겨져있던 작업들과 같은 스레드에 보내지기 때문에 큐에 작업들이 모두 끝나야 실행 가능하다.
하나의 스레드에서만 실행된다.
할당되는 스레드는 운영체제가 결정한다.
큐에 있는 작업들은 앞에 있는 작업들이 끝나야 스레드로 할당된다.
CocurrentQueue.sync
- 메인 스레드의 작업흐름이 큐에 넘긴 작업이 끝날 때까지 멈춰(sync)있다
- 넘겨진 작업들은 큐에 먼저 담겨있던 작업들이 보내진 스레드와 다른 스레드에 보내질 수 있지만
sync이기 때문에 큐에 있는 작업들이 끝나야 실행 가능하다
SerialQueue.async
- 메인 스레드의 작업흐름이 작업을 큐에 넘기자마자 반환된다(async)
- 넘겨진 작업들은 큐에 먼저 담겨 있던 작업들과 같은 스레드에 보내지기 때문에 큐에 있는 작업들이 모두 끝나야 실행 가능하다
- 하나의 스레드에서만 실행된다.
할당되는 스레드는 운영체제가 결정한다.
비동기 이기 때문에 큐에 들어오면 바로 다음 작업이 스레드에 할당된다.
하지만 하나의 스레드에만 작업이 할당되기 때문에 앞의 작업이 끝나야 다음 작업이 실행된다.
ConcurrentQueue.async
- 메인 스레드의 작업흐름이 작업을 큐에 넘기자마자 반환된다(async)
- 앞의 작업을 스레드에 보낸뒤 끝날때 까지 기다리지 않기 때문에 바로 다음 작업들이 다른 스레드로 분배된다.
단 메인스레드처럼 동일한 스레드에 작업들이 할당되면 앞의 작업이 끝나야 뒤의 작업이 실행가능하다.
Task1, Task2, Task3는 동시에 실행될 수 있지만 Task4는 Task이 끝나야만 실행된다.
비동기란 말과 동시란 말이 같은 말인가?
이제 완전 다른말이라는 것을 알 수 있다
async vs sync
작업을 보내는 시점에서 기다릴지 말지에 대해 다루는 것
concurrent vs serial
Queue로 보내진 작업들을 여러개의 스레드로 보낼 것인지 한개의 스레드로 보낼 것인지에 대해 다루는 것
결국 사용하는것은 비동기코드이다.
동기코드는 실행하는것을 기다리기 때문에 메인스레드에서 실행하는 것과 큰 차이가 없기 때문이다.
Thread Pool
- 병렬 처리가 많아지면 스레드 개수가 무한히 증가된다.
- 스레드의 생성과 스케줄링으로 인한 오버헤드가 커져 성능 저하로 이어진다.
- 스레드의 폭증을 막기위해 Thread Pool을 이용한다.
- Thread Pool은 작업 처리에 사용 되는 스레드를 제한된 개수만큼 정해 놓고 작업 큐에 들어오는 작업들을 하나씩 스레드가 맡아 처리한다.
- 작업 처리가 끝난 스레드는 다시 작업 큐에서 새로운 작업을 가져와 처리한다.
- 따라서 작업 처리 요청이 폭증해도 작업 큐라는 곳에 작업이 대기하다가 여유가 있는 스레드가 그것을 처리하므로 스레드의 전체 개수는 일정하며 애플리케이션의 성능도 저하되지 않는다.
- Swift에서는 DispatchQueue가 스레드를 관리한다
참고
iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션큐의 이해 - 인프런 | 강의
iOS Concurrency(동시성) 프로그래밍, 동기 비동기 처리 그리고 GCD/Operation - 디스패치큐와 오퍼레이션
동시성(Concurrency)프로그래밍 - iOS프로그래밍에서 필요한 동기, 비동기의 개념 및 그를 확장한 GCD 및 Operation에 관한 모든 내용을 다룹니다., ✍️ 강의 제작 동기 '왜 동기vs비동기 개념, 직렬vs병
www.inflearn.com
flatMap() 정리하기. map(), compactMap() 과의 차이
flatMap() 정리하기. map(), compactMap() 과의 차이
Swift의 고차함수 중 flatMap()이라는 함수가 있다. 이 함수를 공부하면서 비슷한 이름인 map(), compactMap()과 어떤 차이가 있는지도 정리해야겠다. flatMap() 정리하기. map(), compactMap() 과의 차이 flatMap이
ontheswift.tistory.com
'iOS > Swift' 카테고리의 다른 글
[Swift] Key Value Coding(KVC), Key Value Observing(KVO) (1) | 2024.06.15 |
---|---|
[Swift] RxSwift, Combine 원리 이해하기 (0) | 2024.01.20 |
[Swift] 정규표현식 (1) | 2024.01.12 |
[Swift] Swift에서의 메모리 관리 (0) | 2023.12.28 |
[Swift] Copy-on-Write (1) | 2023.12.28 |
소중한 공감 감사합니다