# 동시성 프로그래밍

## 동시성 프로그래밍

#### 동시성 프로그래밍 목차

* [용어 정리](#용어-정리)
  * 동기와 비동기
  * 직렬처리와 동시처리
* [동시성 프로그래밍](#동시성-프로그래밍)
  * CPU의 개념
  * 병렬과 동시
* [동기, 비동기의 개념](#동기,-비동기의-개념)
* \[직렬과 동시]\(#직렬과 동시)
* [CGD의 개념 및 종류](#CGD의-개념-및-종류)
* [GCD 사용시 주의 사항](#GCD-사용시-주의-사항)
* [Async/await](#Async/await)
* [동시성 프로그래밍의 문제와 해결방안](#동시성-프로그래밍의-문제와-해결방안)
  * 메모리 구조
  * 동시성 프로그래밍과 관련된 문제점
  * 해결 방법

## 용어 정리

### 동기와 비동기

#### 정의

1. 동기
   * task 1이 끝날 때까지 다른 태스크를 실행하지 않는다.
   * 작업을 시작시키고, 끝날 때까지 기다렸다가 다음 일을 진행한다.
2. 비동기
   * 다른 스레드로 task 1을 보내고, task2를 수행한다.

     * 비동기는 task1이 끝날때까지 기다리지 않는다. (즉시 리턴)

     → 메인 스레드(1번 스레드)가 다른 일을 할 수 있다.

#### 비동기처리가 필요한 이유

* iPhone의 경우, 1초에 60번씩 화면을 그려 (60Hz) 화면이 움직이는 것처럼 보이게 만든다.

  그런데 네트워크 통신 등 시간이 오래 걸리는 작업을 동기로 처리하면, 그 작업의 결과를 기다리느라 화면을 그리는 매커니즘이 제대로 동작하지 않게 된다. → 화면이 뚝뚝 끊기는 현상이 발생한다.
* 비동기처리를 통해 부하가 많이 걸리는 일을 동시에 수행하기 위해 비동기 처리가 필요하다.

***

### 직렬처리와 동시처리

#### 정의

1. 직렬 처리
   * Task를 2번 스레드에 모두 보내는 것이다.
   * 다른 하나의 스레드에서 처리하는 것
2. 동시 처리
   * 여러개의 스레드에 보내는 것이다.
   * 스레드의 개수는 시스템이 결정한다.

#### 직렬처리가 필요한 이유

* 태스크가 순서가 중요할 때 사용한다.

#### 병렬 처리가 필요한 이유

* 독립적이지만, 유사한 일을 처리해야할 때 사용

  (게시글을 불러올 때)

***

#### 결론

* 비동기와 동시는 다른 말이다!
  * **기다릴지 말지**에 의한 것 : 동기 / 비동기
  * 1번 스레드 이외의 **스레드 개수**에 의한 것 : 동시 / 직렬

#### 동시성 프로그래밍이 필요한 이유

1. 성능, 반응성을 다루기 위해서
2. 최적화를 위해서

## 동시성 프로그래밍

### 복습

#### 비동기 처리가 필요한 이유

* 테이블을 스크롤할 때마다 버벅임이 발생한다.
* 서버에 데이터를 요청하는 일이 부하가 많이 걸리는 일이기 때문이다.

#### 왜 뚝뚝 끊기는 현상이 일어날까?

* 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개이다\~!

***

#### 앱의 시작 과정 및 동작 원리

1. 앱의 시작 (Launch Time)
   * 사용자가 앱 아이콘을 클릭함
   * main()이 실행됨
   * UIApplicationMain()을 통해 앱 객체가 생성됨
   * 화면을 준비
   * … 초기화 완료 → Running 단계로 이동
2. 실행 중 (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가 관리함을 의미한다.

1. Dispatch Queue
   * GCD (Crand Central DispatchQueue)라고도 한다.
2. Operation Queue

***

### 병렬 / 동시

#### 병렬(Parallel)과 동시성(Concurrency)의 개념

💡 물리적인 스레드

* 물리적인 스레드는 1개이더라도, S/W적 스레드는 여러 개의 객체로 나누어질 수 있다.
* 물리적인 스레드가 1초에 35억번의 일을 할 수 있다고 가정한다.
  * S/W적 스레드 하나 당 1초에 10번의 연산을 할 수 있다는 것이다.
  * Thread pool이라고 한다. (OS에서 알아서 관리)

1. 동시성 (Concurrency)
   * 소프트웨어적인 스레드의 관점
     * 개발자가 신경써야하는 영역이다.
   * 물리적인 Thread를 알아서 switching하며, 빠르게 일을 처리한다.
2. 병렬 (Parallel)
   * 물리적인 스레드에서 실제 동시에 일을 하는 개념이다.
     * 내부적으로 동작하는 것으로 개발자가 신경쓰지 않아도 되는 부분이다.

***

#### 분산처리

* 비동기 처리 / 동시성 프로그래밍!!
  * 성능, 반응성, 최적화와 관련된다.
  * 즉, 화면의 버벅거림 문제를 해결하기 위한 프로그래밍 기법이다.

## 동기, 비동기의 개념

#### 동기 (Sync)

* 다른 스레드에 일을 시켰을 때, 해당 일이 끝날 때 까지 **기다리는 것**이다.
  * block 된 상태로, 다른 일 처리를 할 수 없다.
* 작업이 긴 경우, task1이 끝날 때까지 기다려야 task 2가 실행될 수 있다.

#### 비동기 (Async)

* 다른 스레드에 일을 시킬 때, 해당 일이 끝나는 것을 **기다리지 않고**, 다음 일을 진행하는 것이다.
  * 큐가 알아서 스레드에 태스크를 보낸다.
* task1이 매우 오래 걸려도, task2를 시작할 수 있다는 것이다.

#### 코드레벨에서의 동기와 비동기

* 내부적으로 비동기 처리가 되어있는 함수의 경우
  * 거의 같은 시간 내 모든 함수가 종료된다.
  * 실행 순서와 관계 없이 실행 시간이 짧은 함수의 결과를 먼저 출력한다.

***

#### Blocking, Non Blocking

기본 개념 : CPU 제어권과 관련된 개념이다.

Swift에 Blocking 관련 개념이 있다는 것이 아님 (동기, 비동기만 존재)

1. Blocking (동기)
   * 제어권을 바로 반환하지 않는 것이다.
   * 즉, 2번 Thread에 제어권을 줘서, 1번 Thread는 멈춰있는 상태이다.
2. Non Blocking (비동기)
   * 제어권을 바로 반환하는 것이다. → 다른 일을 할 수 있다.
   * 1번 Thread가 지속적으로 완료 여부를 확인하고, 2번 Thread는 지속적으로 완료 여부를 회신하는 방식이다.

## 직렬과 동시

#### 큐의 종류

1. 직렬 큐 (Serial)
   * 작업을 배치 보내면, 하나의 스레드를 생성한다.
   * 다른 하나의 스레드에서만 작업을 수행한다.
   * **순서가 중요한 작업**을 처리할 때 사용한다
2. 동시 큐 (Concurrent)
   * 작업을 배치 보내면, 여러개의 스레드를 생성하여 수행한다.
   * 독립적이지만, 유사한 작업을 처리할 때 사용한다. (중요도나 성격이 유사할 때)

## GCD 개념 및 종류

#### Dispatch Queue

* Default로 global 큐를 생성하고, 비동기적으로 작동한다.
* 클로저는 작업을 하나로 묶는다.
  * 클로저 내 작업은 순차적으로 진행된다.
* Dispatch Queue는 하나의 task로, 순서가 바뀔 수 있다.
  * Dispatch Queue 내부 closure는 순서가 바뀌지 않음을 의미한다.
* 비동기적 함수를 만들기 위해서 함수 내부에 DispatchQueue를 정의하면 된다.

#### Queue 의 종류

1. DispatchQueue (GCD)
   1. (글로벌) 메인 큐
   2. 글로벌 큐
   3. 프라이빗 큐
2. OperationQueue

#### Dispatch Queue (GCD) 의 종류

1. Global Main Queue
   * 1번 Thread를 의미한다.
   * 직렬(Serial)로 동작한다.
   * UI 업데이트 내용을 처리한다.
   * `DispatchQueue.main`
2. Global Queue
   * 여러 개의 Thread를 사용한다.
   * 서비스 품질과 연관된다.
     * 서비스의 품질이 높을수록, Thread 사용 갯수가 많다.
     * iOS가 알아서 스레드를 배치하고, CPU의 배터리를 집중해서 사용하도록해서 일을 빨리 끝내도록 한다.
     * 서비스의 품질이 높을수록 신경을 많이 쓰기는 하지만, 먼저 끝난다고 보장할 수 없다.
     * 6가지 Qos 종류가 존재한다.
   * `DispatchQueue.global(qos: )`
     * .default == global
3. Private Queue (custom)
   * Qos를 설정 가능하다.
   * `DisparchQueue(label: “”)`

## GCD 사용시 주의 사항

#### 반드시 메인 큐에서 처리해야하는 작업

* Thread 2의 작업 결과를 반드시 Thread1에 보내줘야할 때
  * Main Thread가 화면을 다시 그리는 역할이다.
    * ⇒ UI와 관련된 일들은 다시 Main Thread에 보내주어야한다.
    * UI 관련 작업은 메인 쓰레드에서 하지 않으면, 에러가 발생한다.
  * URL session은 내부적으로 비동기로 처리된 함수이다.
  * 따라서 이미지 표시 관련 코드는 반드시 메인 스레드로 보내주어야 한다.

***

#### 컴플리션 핸들러 존재 이유

**콜백함수를 제대로 사용해야한다**

* main Thread는 일을 시킨 후, 작업의 종료를 기다리지 않기 때문이다.
* 비동기 작업이 끝난 후, 원래 있던 위치에 리턴하기 때문에, 비동기 작업이 끝나는 시점을 파악해야한다.

  * 비동기적 함수는 return 타입으로 설계할 수 없어서 콜백함수로 설계해야한다.
    * 일반적으로 애플이 설계해놓은 클로저 이름이 completionHandler이다.
  * 즉시리턴하므로, 데이터 리턴이 아닌 클로저를 실행하는 것이 적절하다.
    * 함수 내부의 작업이 끝나면 클로저가 실행될 수 있도록 설계하는 것이 적절하다.
    * 즉, 함수 내부의 일이 끝나기 전에 return하므로, 항상 nil이 반환되기 때문이다. 이에 비동기적 작업을 할 때에는 클로저를 호출하는 것이 바람직한 것이다.

  ```swift
  // 올바른 비동기함수의 설계 - 콜백 함수의 사용, return형으로 설계하면 안 된다. (async 를 선언하면 가능하다)

  func pro(with wrulString: String, **completionhandler: @escaping(UIImage?) -> Void**) {
      

      // 어쩌고

          **completionHandler(photoImage)** //closure의 실행
      }.resume()
  }
  ```

***

#### weak, strong 캡처의 주의

* 캡처 리스트 내에서 weak self로 선언하지 않으면 강한참조가 된다.
  * 일반적인 경우 weak self로 선언하는 것을 권장한다.
  * 서로를 가리키는 경우, 메모리 누수가 발생할 수 있다.
  * 메모리 누수가 발생하지 않아도, 클로저의 수명주기가 길어지는 현상이 발생할 수 있다.

#### 예제

```swift
class ViewController: UIViewController {
    
    var name: String = "뷰컨"
    
    func doSomething() {
        DispatchQueue.global().async {
            sleep(3)
            print("글로벌큐에서 출력하기: \(self.name)")
        }
    }
    
    deinit {
        print("\(name) 메모리 해제")
    }
}

func localScopeFunction() {
    let vc = ViewController()
    vc.doSomething()
}

localScopeFunction()
```

* Global Queue closure가 강하게 캡처되어 뷰 컨트롤러의 RC가 유지된다.
  * 뷰 컨트롤러가 해제되어도, 3초 후 출력한 후 해제된다.
  * 강한 순환 참조가 일어나지는 않았으나, 뷰컨트롤러가 필요 없음에도 불구하고 오래 머무르게 된다.
  * 뷰 컨트롤러가 사라졌음에도 출력하는 일을 계속 한다.

```swift
class ViewController1: UIViewController {
    
    var name: String = "뷰컨"
    
    func doSomething() {
        // 강한 참조 사이클이 일어나지 않지만, 굳이 뷰컨트롤러를 길게 잡아둘 필요가 없다면
        // weak self로 선언
        DispatchQueue.global().async { **[weak self]** in
            guard let weakSelf = self else { return }
            sleep(3)
            print("글로벌큐에서 출력하기: \(weakSelf.name)")
        }
    }
    
    deinit {
        print("\(name) 메모리 해제")
    }
}

func localScopeFunction1() {
    let vc = ViewController1()
    vc.doSomething()
}

localScopeFunction1()
```

* 뷰컨트롤러를 오래동안 잡아두지 않음
* 뷰컨트롤러가 사라지면 ===> 출력하는 일을 계속하지 않도록 할 수 있음 (if let 바인딩 또는 guard let 바인딩까지 더해서 return 가능하도록)

***

#### 동기함수를 비동기적으로 동작하는 함수로 변형

1. 작업을 오랫동안 실행하는 함수가 있을 때
   * 작업을 오랫동안 실행하는데, 동기적으로 동작하는 함수를 비동기적으로 만들어 반복적으로 사용하도록 만든다.
   * 내부적으로 다른 큐로 비동기적으로 보내서 처리한다.
   * 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을 실행할 수 있다. (깔끔한 코드 작성이 가능)
* 비동기적 코드가 이어져있을 때 코드의 들여쓰기가 연결되는 것을 방지할 수 있다.
