###### tags: `2020 Boostcamp` # Day10-학습정리 ### 스스로 확인할 사항 - **멀티 스레드 스케줄링 방식에 대해 학습하고 정리한다.** - 프로세스 스케줄링과 정책은 동일함 - `Preemtive vs Non-Preemtive` - Preemtive(선점 방식): 실행중인 스레드가 강제적으로 실행권을 빼앗김 - Non-Preemtive(비선점 방식): 실행중인 스레드가 자발적으로 실행권을 내려놓음 - `선입 선처리 스케줄링(FCFS)` - Ready queue에 등록된 순서 그대로 스레드를 처리 - 실행중인 스레드가 다른 스레드에 의해 선점될 수 없음 - `순환 처리 스케줄링(RR: Round-Robin)` - 스레드에 할당 시간을 부여하여 실행 시간을 제한함 - 시분할 시스탬을 위해 개발된 선점 방식의 스케줄링 - `우선순위 큐 스케줄링` - 스레드에 우선순위를 부여하고 높은 우선순위를 가진 스레드부터 실행 - 우선순위가 같을 때 CPU의 할당 시간을 나눔 - 선점 방식 스케줄링 - **각자 운영체제 스레드 동작 방식과 본인이 작성한 스레드 스케줄링 방식을 비교한다.** - Mac OS 는 priority 기반 스레드 스케줄링 알고리즘을 사용 - 스레드가 높은 우선순위를 가졌다고 해서 특정한 실행 시간을 보장받는 것은 아님 - 스레드의 우선순위를 높히면 다른 스레드에서 starvation이 발생할 확률 높으므로 default로 유지하는게 좋음 - 미션때 작성한 스케줄링은 RR이고 priorty가 없음 - **스레드를 무제한으로 만들수 없다면, 프로세스가 많아질 때 성능 향상을 할 수 있는 방법이 무엇일까?** - 프로세스간 shared data 최소화 - I/O 작업을 최소화 하거나, 해당 작업만을 위한 프로세스를 분리 - CPU Bursttime을 정확하게 측정할 수 없지만, 개략적인 카테고리를 나눠 합리적인 우선순위를 부여 - **DispatchQueue 동작 방식에 대해 학습하고 정리한다.** > * Swift에서 Thread 관련 작업은 `Grand Central Dispatch API (GCD)`를 통해 처리 > * Closure로 표현된 작업을 큐에 태우고, 해당 큐를 특정 Thread에 출격시켜 실행하는 방식 > * GCD를 이용해서 multi-threading 작업을 할때는 적절한 Queue와 QoS를 골라야 함 > * GCD는 Thread safe한 Dispatch Queue를 제공함 - `Dispatch Queue` 1. Main(Serial) * Main thread 에서 처리되는 serial queue 2. Global(Concurrent) * 전체 시스템에 공유되는 concurrent queue * 작업 우선순위를 사전에 정의한 QoS를 활용함 ![](https://i.imgur.com/ghTEoso.png) - `Quality of Service (QoS)` 1. userInteractive * 중요도가 높고 즉각적인 반응이 요구되는 작업을 위해 가장 자원을 많이 쓰도록 함 * global queue 항목이지만 main thread에서 실행됨 * UI update, Event handling 등 2. userInitiated * userInteractive 보다는 중요도가 떨어지지만 유저가 빠른 결과를 기대할 때 사용 * 유저가 실행시킨 작업으로 Async하게 처리해야 할 일들에 사용 3. utility * 시간이 다소 오래 걸리는 작업을 처리 * 유저에게 처리가 진행되는 모습을 보일 수도 있음 * 계산, I/O, 네트워킹, 데이터 피드 등 4. background * 유저가 인지하지 못하도록 뒷단에서 실행되는 작업 * 유저 인터렉션이 불필요하고 급하지 않은 작업 ### 다같이 확인할 사항 - **플랫폼에서 정확도 높은 타이머Timer를 구현하기 위한 여러 방식에 대해 학습한다.** - `Timer`와 `DispatchSourceTimer` 보다 `mach_absolute_time` 을 사용해 구현하는 것이 정확함 - **멀티 스레드가 공용 리소스에 접근할 때 임계구역을 다루는 방식(Semaphore, Mutex 등)에 대해 학습하고, 어떤 경우에 사용해야 하는지 토론한다.** - `Semaphore` - 정수값을 가지는 변수라 볼 수 있음 - `semWait`: 세마포어 값을 감소시킴. 값이 음수가 되면 `semWait`를 호출한 프로세스는 블록 됨 - `semSignal`: 세마포어 값을 증가시킴. 값이 양수가 아니면(0이거나 음수면), semWait 연산에 의해 블록된 프로세스들을 깨움 ```c++ struct semaphore { int count; queueType queue; }; void semWait (semaphore s) { s.count--; if (s.count < 0) { /* 이 구역으로 들어왔다는 것은 현재 프로세스(혹은 쓰레드)가 공유 자원에 접근할 수 없다는 것을 의미*/ /* 요청한 프로세스를 s.queue에 연결 */ /* 요청한 프로세스를 블록 상태로 전이 시킴*/ } } void semSignal (semaphore s) { s.count++; if (s.count <= 0) { /* count가 0보다 작거나 같다는 것은 대기하고 있는 프로세스(또는 스레드)가 존재한다는 것을 의미*/ /* s.queue에 연결되어 있는 프로세스를 큐에서 제거 */ /* 프로세스의 상태를 실행 가능으로 전이시키고 ready list에 연결 */ } } ``` - Swift의 세마포어: GCD Dispatch에서 제공 ```swift let semaphore = DispatchSemaphore(value: 3) for i in 1...10 { DispatchQueue.global().async { semaphore.wait() defer { semaphore.signal() } // 이미지 다운로드에 3초가 걸린다고 가정 print("Downloading image \(i)") Thread.sleep(forTimeInterval: 3) print("Image \(i) downloaded") } ``` * `Mutex` * 이진 세마포어와 같이 초기값을 1과 0으로 가짐 * 임계영역에 들어갈 때 `락(lock)`을 걸어 다른 프로세스(혹은 쓰레드)가 접근하지 못하도록 함 * 임계영역에서 나와 해당 락을 `해제(unlock`) ```c++ mutex = 1; void lock () { while (mutex != 1) { /* mutex 값이 1이 될 때까지 기다립니다.*/ } /* 이 구역에 도착했다는 것은 mutex 값이 1이라는 것을 의미. 따라서 이제 뮤텍스 값을 0으로 만들어 다른 프로세스(혹은 쓰레드)가 접근하지 못하도록 막아야 함. */ mutex = 0; } void unlock() { /* 임계 구역에서 나온 프로세스는 다른 프로세스가 접근할 수 있도록 락을 해제.*/ mutex = 1; } ``` ✅ 공유된 자원의 데이터를 여러 프로세스가 접근하는 것을 막을 때: 세마포어 ✅ 공유된 자원의 데이터를 여러 쓰레드가 접근하는 것을 막을 때: 뮤텍스