###### 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를 활용함

- `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;
}
```
✅ 공유된 자원의 데이터를 여러 프로세스가 접근하는 것을 막을 때: 세마포어
✅ 공유된 자원의 데이터를 여러 쓰레드가 접근하는 것을 막을 때: 뮤텍스