동시성 프로그래밍
동시성 프로그래밍
동시성 프로그래밍 목차
동기와 비동기
직렬처리와 동시처리
CPU의 개념
병렬과 동시
[직렬과 동시](#직렬과 동시)
메모리 구조
동시성 프로그래밍과 관련된 문제점
해결 방법
용어 정리
동기와 비동기
정의
동기
task 1이 끝날 때까지 다른 태스크를 실행하지 않는다.
작업을 시작시키고, 끝날 때까지 기다렸다가 다음 일을 진행한다.
비동기
다른 스레드로 task 1을 보내고, task2를 수행한다.
비동기는 task1이 끝날때까지 기다리지 않는다. (즉시 리턴)
→ 메인 스레드(1번 스레드)가 다른 일을 할 수 있다.
비동기처리가 필요한 이유
iPhone의 경우, 1초에 60번씩 화면을 그려 (60Hz) 화면이 움직이는 것처럼 보이게 만든다.
그런데 네트워크 통신 등 시간이 오래 걸리는 작업을 동기로 처리하면, 그 작업의 결과를 기다리느라 화면을 그리는 매커니즘이 제대로 동작하지 않게 된다. → 화면이 뚝뚝 끊기는 현상이 발생한다.
비동기처리를 통해 부하가 많이 걸리는 일을 동시에 수행하기 위해 비동기 처리가 필요하다.
직렬처리와 동시처리
정의
직렬 처리
Task를 2번 스레드에 모두 보내는 것이다.
다른 하나의 스레드에서 처리하는 것
동시 처리
여러개의 스레드에 보내는 것이다.
스레드의 개수는 시스템이 결정한다.
직렬처리가 필요한 이유
태스크가 순서가 중요할 때 사용한다.
병렬 처리가 필요한 이유
독립적이지만, 유사한 일을 처리해야할 때 사용
(게시글을 불러올 때)
결론
비동기와 동시는 다른 말이다!
기다릴지 말지에 의한 것 : 동기 / 비동기
1번 스레드 이외의 스레드 개수에 의한 것 : 동시 / 직렬
동시성 프로그래밍이 필요한 이유
성능, 반응성을 다루기 위해서
최적화를 위해서
동시성 프로그래밍
복습
비동기 처리가 필요한 이유
테이블을 스크롤할 때마다 버벅임이 발생한다.
서버에 데이터를 요청하는 일이 부하가 많이 걸리는 일이기 때문이다.
왜 뚝뚝 끊기는 현상이 일어날까?
iPhone의 화면 주사율은 60Hz
1초에 60번 화면을 다시 그린다.
특정 메커니즘에 의해 화면을 계속 다시 그리면서, 움직이는 것처럼 보이게 만든다.
비동기처리를 하지 않으면, 메커니즘이 제대로 동작할 수 없어서 버벅임이 발생한다.
CPU의 개념
코어 / 쓰레드 / 클럭의 개념
1개의 CPU (1 Core, 1 Thread)
물리적인 속도를 높이기 위해 Clock(진동)의 속도를 높였다.
발열 문제 및 배터리 이슈 발생
CPU 개수를 늘리기 시작함
2개의 CPU (2 Core, 2 Thread)
4개의 CPU (4 Core, 4 Thread)
8, 10 …
4 Core 8 Thread
Core에 Thread 개수를 늘렸음 (하이퍼 쓰레딩 기술)
Thread : 실제로 일을 하는 부분임. → 1Core에 2개의 Thread를 붙이게 되면서 8개의 core가 있는 효과를 가져왔다.
현대 PC 6Core, 12 Thread
물리적으로 일을 처리하는 부분은 12개이다~!
앱의 시작 과정 및 동작 원리
앱의 시작 (Launch Time)
사용자가 앱 아이콘을 클릭함
main()이 실행됨
UIApplicationMain()을 통해 앱 객체가 생성됨
화면을 준비
… 초기화 완료 → Running 단계로 이동
실행 중 (Running Time)
앱의 동작
event loop라는 런루프를 생성하고, 무한 반복문이 실행된다.
event: 사용자가 전달하는 값들 (터치, 화면 회전 등)
런 루프 : 이벤트 핸들링 객체로 이벤트를 파악하고 적절한 함수를 실행시킨다.
무한 반복문이 일처리를 하고, update cycle(1초에 60회)에 맞추어 업데이트 하게 된다.
화면을 그리는 일 = main Thread에서 담당한다.
main Thread의 역할
(앱이 시작될 때 앱을 담당하는 메인 런 루프가 생성된다.)
메인 쓰레드는 이벤트 처리를 담당한다.
이벤트에 맞춰 어떤 함수를 실행시킬 것인지 선택하고 실행한다.
실행 결과를 화면에 보여주며, 필요 시 화면을 다시 그리게 된다. (main Thread)
소프트웨어적인 Thread (NSThread 객체)
메인 쓰레드는 1초에 60번 화면을 다시 그려야하는 역할을 가지고 있다.
직접적으로 화면을 그리진 않지만, 렌더링 프로세스 역할을 한다는 것임.
이에 맞는 업데이트 주기를 가지고 있다. (Update cycle)
이런 과정에서, 메인 스레드에서 너무 오래 걸리는 작업을 한다면?
1초에 60번 화면을 다시 그리는 역할도 수행해야하기 때문에 화면이 버벅거림
task 사이 비어있는 시간에만 화면을 다시 그릴 수 있기 때문이다.
이에 1번 쓰레드 뿐만 아니라, 다른 쓰레드에서도 일을 시키는 방법으로 코딩을 해야함
분산처리를 어떻게 하는지에 대한 코딩 방법
비동기처리, 동시성 프로그래밍이라고 한다.
iOS의 동시성 처리
작업을 대기행렬(Queue)에 보내기만하면, 운영체제(iOS)가 알아서 분산처리(동시적 처리)를 한다.
Queue (FIFO 구조)
Queue에 들어오는 즉시 다른 스레드에 바로 배치한다.
즉, 작업을 쌓았다가 보내는 것이 아니라 작업을 즉시 스레드에 배치한다.
iOS 프로그래밍의 대기열 (Queue)의 종류
💡 iOS에서는 직접적으로 쓰레드를 관리하는 개념이 아니라, 대기열 개념을 이용해서 작업을 분산처리한다. 이후 iOS가 알아서 쓰레드의 개수를 관리하게 된다. 즉, 개발자들은 Task를 Queue에 보내기만 하면, iOS가 관리함을 의미한다.
Dispatch Queue
GCD (Crand Central DispatchQueue)라고도 한다.
Operation Queue
병렬 / 동시
병렬(Parallel)과 동시성(Concurrency)의 개념
💡 물리적인 스레드
물리적인 스레드는 1개이더라도, S/W적 스레드는 여러 개의 객체로 나누어질 수 있다.
물리적인 스레드가 1초에 35억번의 일을 할 수 있다고 가정한다.
S/W적 스레드 하나 당 1초에 10번의 연산을 할 수 있다는 것이다.
Thread pool이라고 한다. (OS에서 알아서 관리)
동시성 (Concurrency)
소프트웨어적인 스레드의 관점
개발자가 신경써야하는 영역이다.
물리적인 Thread를 알아서 switching하며, 빠르게 일을 처리한다.
병렬 (Parallel)
물리적인 스레드에서 실제 동시에 일을 하는 개념이다.
내부적으로 동작하는 것으로 개발자가 신경쓰지 않아도 되는 부분이다.
분산처리
비동기 처리 / 동시성 프로그래밍!!
성능, 반응성, 최적화와 관련된다.
즉, 화면의 버벅거림 문제를 해결하기 위한 프로그래밍 기법이다.
동기, 비동기의 개념
동기 (Sync)
다른 스레드에 일을 시켰을 때, 해당 일이 끝날 때 까지 기다리는 것이다.
block 된 상태로, 다른 일 처리를 할 수 없다.
작업이 긴 경우, task1이 끝날 때까지 기다려야 task 2가 실행될 수 있다.
비동기 (Async)
다른 스레드에 일을 시킬 때, 해당 일이 끝나는 것을 기다리지 않고, 다음 일을 진행하는 것이다.
큐가 알아서 스레드에 태스크를 보낸다.
task1이 매우 오래 걸려도, task2를 시작할 수 있다는 것이다.
코드레벨에서의 동기와 비동기
내부적으로 비동기 처리가 되어있는 함수의 경우
거의 같은 시간 내 모든 함수가 종료된다.
실행 순서와 관계 없이 실행 시간이 짧은 함수의 결과를 먼저 출력한다.
Blocking, Non Blocking
기본 개념 : CPU 제어권과 관련된 개념이다.
Swift에 Blocking 관련 개념이 있다는 것이 아님 (동기, 비동기만 존재)
Blocking (동기)
제어권을 바로 반환하지 않는 것이다.
즉, 2번 Thread에 제어권을 줘서, 1번 Thread는 멈춰있는 상태이다.
Non Blocking (비동기)
제어권을 바로 반환하는 것이다. → 다른 일을 할 수 있다.
1번 Thread가 지속적으로 완료 여부를 확인하고, 2번 Thread는 지속적으로 완료 여부를 회신하는 방식이다.
직렬과 동시
큐의 종류
직렬 큐 (Serial)
작업을 배치 보내면, 하나의 스레드를 생성한다.
다른 하나의 스레드에서만 작업을 수행한다.
순서가 중요한 작업을 처리할 때 사용한다
동시 큐 (Concurrent)
작업을 배치 보내면, 여러개의 스레드를 생성하여 수행한다.
독립적이지만, 유사한 작업을 처리할 때 사용한다. (중요도나 성격이 유사할 때)
GCD 개념 및 종류
Dispatch Queue
Default로 global 큐를 생성하고, 비동기적으로 작동한다.
클로저는 작업을 하나로 묶는다.
클로저 내 작업은 순차적으로 진행된다.
Dispatch Queue는 하나의 task로, 순서가 바뀔 수 있다.
Dispatch Queue 내부 closure는 순서가 바뀌지 않음을 의미한다.
비동기적 함수를 만들기 위해서 함수 내부에 DispatchQueue를 정의하면 된다.
Queue 의 종류
DispatchQueue (GCD)
(글로벌) 메인 큐
글로벌 큐
프라이빗 큐
OperationQueue
Dispatch Queue (GCD) 의 종류
Global Main Queue
1번 Thread를 의미한다.
직렬(Serial)로 동작한다.
UI 업데이트 내용을 처리한다.
DispatchQueue.main
Global Queue
여러 개의 Thread를 사용한다.
서비스 품질과 연관된다.
서비스의 품질이 높을수록, Thread 사용 갯수가 많다.
iOS가 알아서 스레드를 배치하고, CPU의 배터리를 집중해서 사용하도록해서 일을 빨리 끝내도록 한다.
서비스의 품질이 높을수록 신경을 많이 쓰기는 하지만, 먼저 끝난다고 보장할 수 없다.
6가지 Qos 종류가 존재한다.
DispatchQueue.global(qos: )
.default == global
Private Queue (custom)
Qos를 설정 가능하다.
DisparchQueue(label: “”)
GCD 사용시 주의 사항
반드시 메인 큐에서 처리해야하는 작업
Thread 2의 작업 결과를 반드시 Thread1에 보내줘야할 때
Main Thread가 화면을 다시 그리는 역할이다.
⇒ UI와 관련된 일들은 다시 Main Thread에 보내주어야한다.
UI 관련 작업은 메인 쓰레드에서 하지 않으면, 에러가 발생한다.
URL session은 내부적으로 비동기로 처리된 함수이다.
따라서 이미지 표시 관련 코드는 반드시 메인 스레드로 보내주어야 한다.
컴플리션 핸들러 존재 이유
콜백함수를 제대로 사용해야한다
main Thread는 일을 시킨 후, 작업의 종료를 기다리지 않기 때문이다.
비동기 작업이 끝난 후, 원래 있던 위치에 리턴하기 때문에, 비동기 작업이 끝나는 시점을 파악해야한다.
비동기적 함수는 return 타입으로 설계할 수 없어서 콜백함수로 설계해야한다.
일반적으로 애플이 설계해놓은 클로저 이름이 completionHandler이다.
즉시리턴하므로, 데이터 리턴이 아닌 클로저를 실행하는 것이 적절하다.
함수 내부의 작업이 끝나면 클로저가 실행될 수 있도록 설계하는 것이 적절하다.
즉, 함수 내부의 일이 끝나기 전에 return하므로, 항상 nil이 반환되기 때문이다. 이에 비동기적 작업을 할 때에는 클로저를 호출하는 것이 바람직한 것이다.
weak, strong 캡처의 주의
캡처 리스트 내에서 weak self로 선언하지 않으면 강한참조가 된다.
일반적인 경우 weak self로 선언하는 것을 권장한다.
서로를 가리키는 경우, 메모리 누수가 발생할 수 있다.
메모리 누수가 발생하지 않아도, 클로저의 수명주기가 길어지는 현상이 발생할 수 있다.
예제
Global Queue closure가 강하게 캡처되어 뷰 컨트롤러의 RC가 유지된다.
뷰 컨트롤러가 해제되어도, 3초 후 출력한 후 해제된다.
강한 순환 참조가 일어나지는 않았으나, 뷰컨트롤러가 필요 없음에도 불구하고 오래 머무르게 된다.
뷰 컨트롤러가 사라졌음에도 출력하는 일을 계속 한다.
뷰컨트롤러를 오래동안 잡아두지 않음
뷰컨트롤러가 사라지면 ===> 출력하는 일을 계속하지 않도록 할 수 있음 (if let 바인딩 또는 guard let 바인딩까지 더해서 return 가능하도록)
동기함수를 비동기적으로 동작하는 함수로 변형
작업을 오랫동안 실행하는 함수가 있을 때
작업을 오랫동안 실행하는데, 동기적으로 동작하는 함수를 비동기적으로 만들어 반복적으로 사용하도록 만든다.
내부적으로 다른 큐로 비동기적으로 보내서 처리한다.
DispatchQueue로 감싸서 처리하면 가능하다.
return이 있는 형태라면, closure로 감싸서 reuturn 할 수 있다.
비동기적으로 구현된 메서드
일반적으로 대부분의 네트워킹 등 오래걸리는 API는 비동기적으로 구현 돼 있다.
그러나, 그렇지 않아 DispatcQueue로 클로저를 보내 명시적으로 비동기처리가 필요한 경우도 있다.
Async/await
Async await
Swift 5.5부터 사용가능한 방법이다.
비동기함수를 이어서 처리하는 것이다. (코드 상의 불편함을 해결한 것임)
작업이 끝나는 시점에 Completion 블럭을 실행시키는 것이다.
Completion 블럭에서 Completion 블럭을 넣을 수 있다.
장점
들여쓰기가 필요 없다
죽음의 피라미드 (Pyramid of doom을 없애준다, 들여쓰기를 계속해야하는 것)
리턴 시점을 기다리지 않아도 된다
클로저를 통해 전달하지 않아도, return을 실행할 수 있다. (깔끔한 코드 작성이 가능)
비동기적 코드가 이어져있을 때 코드의 들여쓰기가 연결되는 것을 방지할 수 있다.
동시성 프로그래밍의 문제와 해결방안
Async await
Swift 5.5부터 사용가능한 방법이다.
비동기함수를 이어서 처리하는 것이다. (코드 상의 불편함을 해결한 것임)
작업이 끝나는 시점에 Completion 블럭을 실행시키는 것이다.
Completion 블럭에서 Completion 블럭을 넣을 수 있다.
장점
들여쓰기가 필요 없다
죽음의 피라미드 (Pyramid of doom을 없애준다, 들여쓰기를 계속해야하는 것)
리턴 시점을 기다리지 않아도 된다
클로저를 통해 전달하지 않아도, return을 실행할 수 있다. (깔끔한 코드 작성이 가능)
비동기적 코드가 이어져있을 때 코드의 들여쓰기가 연결되는 것을 방지할 수 있다.
Last updated