🕖 스케줄러
Multi-Programming의 목적은 프로세스의 CPU 이용을 최대화하여 사용되지 않는 CPU를 최소화하는 것이다. 이 때 프로세스를 적절하게 배치하는 것
스케줄링 큐 (Scheduling Queue)
프로세스를 스케줄링 하기 위해 3가지 큐(Queue)를 사용한다.
- Job Queue : 시스템 내에 있는 모든 프로세스의 집합
- Ready Queue : 메인 메모리 내에 있으면서 Ready 상태에서 CPU의 할당을 기다리는 프로세스의 집합
- Device Queue : Device I/O 작업을 대기하고 프로세스의 집합
=> 프로세스는 스케줄러에 의해 적절한 큐로 배치된다.
장기 스케줄러 (Long-term Scheduler)
작업 스케줄러(Job Scheduler)라고도 불리며, 어떤 프로세스를 Ready Queue로 보낼지 결정하는 스케줄러이다.
메모리는 한정되어 있기 때문에, 실행할 수 있는 프로세스보다 많은 프로세스가 메모리에 올라오면 대용량 메모리(일반적으로 하드 디스크)에 임시로 저장된다. 장기 스케줄러는 하드 디스크의 프로세스 중 하나를 선택하여 메모리를 할당하고 Ready Queue로 보내는 역할을 한다.
설명
- 메모리와 디스크 사이의 스케줄링을 담당한다.
- 프로세스에 메모리를 할당한다.
- Degree of Multiprogramming을 제어한다.
- 즉, 현재 메인 메모리에 존재하는 활성화된 일(active job)의 개수를 제어한다.
- 프로세스가 끝날 때 실행되므로, 실행 빈도가 낮으며 상대적으로 속도가 느린 것이 허용된다.
단기 스케줄러 (Short-term Scheduler)
CPU Scheduler라고도 불리며, 어떤 프로세스에 CPU를 할당할지 결정하는 스케줄러이다.
CPU는 한 번에 하나의 작업만 처리할 수 있다. 따라서, Ready Queue에서 CPU를 할당할 하나의 프로세스를 선택해야 한다.
설명
- Ready 상태에 있는 작업 중에서 프로세스를 선택하여 CPU를 할당한다.
- CPU와 메모리 사이의 스케줄링을 담당한다.
- 일반적인 스케줄러는 단기 스케줄러를 의미하며, 단기 스케줄러는 미리 정한 스케줄링 알고리즘에 따라 CPU를 할당할 프로세스를 선택한다.
- CPU를 낭비하지 않기 위해 프로세스를 기다리는 동안 다른 프로세스를 실행해야 한다.
즉, 밀리 세컨드(ms) 이하의 시간 단위로 매우 빈번하게 호출되며, 실행 속도가 충분히 빨라야 한다.
중기 스케줄러 (Medium-term Scheduler)
Swapper라고도 불리며, 메모리에 적재된 프로세스 수를 관리하는 스케줄러이다.
너무 많은 프로세스에 메모리를 할당해 시스템의 성능이 저하되는 문제를 해결하기 위해 메모리에 적재된 프로세스의 수를 동적으로 조절하기 위해 추가된 스케줄러이다.
💡 스와핑
- 스왑 영역 : 메모리가 부족할 때, 메모리처럼 사용하는 디스크 공간
- swap-out : 일부 프로세스를 메모리에서 디스크로 보내는 것
- swap-in : 메모리에 여유가 생기면 다시 메모리에 적재하는 것
문제
- 메모리에 너무 많은 프로세스가 할당되어 프로세스 당 보유하고 있는 메모리가 적어지게 되면 CPU 수행에 필요한 메모리 공간도 확보하지 못하는 상황이 발생할 수 있다. 이렇게 되면 디스크 I/O가 수시로 발생되어 시스템의 성능이 심각하게 저하될 수 있다.
설명
- 메모리에 올라와 있는 일부 프로세스를 디스크의 스왑 영역에 저장해둔다. (swap-out)
- blocked 상태에 있는 프로세스들을 우선적으로 스왑 아웃시킨다.
- blocked 상태의 프로세스를 스왑 아웃해도 문제가 해결되지 않을 경우, 중기 스케줄러는 타이머 인터럽트가 발생해 Ready Queue로 이동하는 프로세스를 추가적으로 스왑 아웃시킨다.
- 중기 스케줄러의 등장으로
suspended, stopped 상태가 추가되었으며, 중지 상태의 프로세스는 메모리를 통째로 빼앗기고 디스크로 스왑 아웃된다.
중지 상태는 suspended ready 와 suspended block가 있다.
- 중지 준비 (suspended ready) : 준비 상태의 프로세스가 중기 스케줄러에 의해 디스크로 swap out
- 봉쇄 중지 (suspended block) : 봉쇄 상태의 프로세스가 중기 스케줄러에 의해 디스크로 swap out
❓ 현대 OS에는 단기, 중기, 장기 스케줄러를 모두 사용하고 있나요?
장기 스케줄러
- 현대 운영체제에서는 잘 사용되지 않으며, 대부분 시분할 방식인 Round Robin을 사용한다.
- 시분할 방식에서 프로세스는 시작과 동시에 메모리를 할당해 Ready Queue에 넣는다.
중기 스케줄러
- 가상 메모리를 사용하면 프로세스의 전체가 아닌 일부만 실제 메모리에 올라와도 된다.
즉, 실제 메모리 용량보다 큰 프로그램을 실행 시킬 수 있고, 메모리의 크기에 제약이 없어졌기 때문에 가상 메모리 시스템에서 중요성이 낮아졌다.
단기 스케줄러
❓ 프로세스의 스케줄링 상태에 대해 설명해 주세요.

1. 프로세스 생성 (new)
- 프로세스가 생성된 상태로, 생성만 되었고, 아직 실행되기 위한 자원을 할당받지 못한 상태이다.
2. 실행 가능(Ready)
- 프로세스가 실행을 기다리는 상태로, 프로세스가 실행되기 위해 필요한 자원을 모두 할당받았으며, 실행을 위한 준비가 완료되었지만, CPU를 할당받지 못한 상태이다.
- 이 상태에서는 CPU를 할당받기 위해 스케줄링 대기열(Queue)에 들어가게 된다.
3. 실행 상태(Running)
- 프로세스가 CPU를 할당받아 실제로 코드를 실행하는 상태이다.
- 프로세스가 작업을 처리하고, 결과를 만들어낸다.
- 선점(preemptive) 시스템에서는 OS에 의해 다시 Ready로 돌아갈 수도 있다.
4. 대기 (Blocked)
- 프로세스 처리 중에 작업 시간이 초과되거나 자원 사용을 위해 대기해야 하는 이벤트가 발생하여 프로세스가 잠시 멈춘 상태이다.
- 이 상태에서는 CPU를 사용하지 않으며, 특정 자원을 사용할 수 있을 때까지 실행을 멈추고, 다시 대기열(Ready Queue)로 들어가게 되며, 프로세스 처리가 가능한 상태가 되면 실행(Running) 상태로 변경된다.
5. 종료(Terminated, exit)
- 프로세스의 실행이 완료되어 종료된 상태이다. 이 상태에서는 할당된 자원이 해제되고, 프로세스의 메모리 공간은 운영체제에 반환된다.
6. 일시중단 (Suspended/Swapped)
- 메모리가 부족하거나 OS 정책에 따라 디스크로 내려간 상태이다.
- 실행이 불가능한 상태이므로 다시 메모리로 올라와야 Ready 또는 Blocked로 복귀할 수 있다.
- 이 상태는 Ready-Suspended, Blocked-Suspended로 나눌 수 있다.
❓ preemptive/non-preemptive 에서 존재할 수 없는 상태가 있을까요?
모든 상태는 존재할 수 있다 !
하지만, 선점형은 Running -> Ready 상태 전이가 가능하지만, 비선점형에서는 Running -> Ready 전이가 불가하며, Running -> Waiting/Terminated 상태 전이가 가능하다.
1️⃣ Preemptive (선점형)
운영체제가 강제로 CPU를 빼앗을 수 있는 방식
- CPU를 할당받은 프로세스가 실행 중이어도, 운영체제가 타이머, 우선순위 등을 기준으로 CPU를 뺏을 수 있다.
- 다른 프로세스가 더 급할 경우, 실행 중인 프로세스를 Ready 상태로 두고 CPU를 넘긴다.
2️⃣ Non-Preemptive (비선점형)
프로세스가 스스로 CPU를 내려놓을 때까지 기다리는 방식
- 한 프로세스가 CPU를 차지하면, 스스로 I/O 요청하거나 종료할 때까지 계속 실행한다.
- OS는 강제로 CPU를 뺏지 않는다. 즉, Running -> Ready 전이가 없으며, Running -> Waiting/Terminated와 같은 상태 전이가 가능하다.
❓ Memory가 부족할 경우, Process는 어떠한 상태로 변화할까요?
메모리가 부족할 경우, Blocked 상태의 프로세스가 우선적으로 Suspended 상태로 전이된다. (Swap-out) 만약, 모든 Blocked 프로세스를 이미 스왑 아웃했는데도 여전히 메모리가 부족한 경우, 혹은 특정한 정책으로 Ready 상태의 프로세스를 내보내는 경우도 있다.
이후, 메모리가 여유로워졌을 때는 다시 Suspended -> Blocked/Ready로 Swap-In된다.
🔁 컨텍스트 스위칭 (Context Switching)
컨텍스트 스위칭(Context Switching)은 CPU가 하나의 작업(프로세스 또는 스레드)을 중지하고, 다른 작업을 수행하기 위해 작업의 상태(Context)를 저장하고 복원하는 과정이다.
CPU는 한 번에 하나의 작업만 처리할 수 있기 때문에, 여러 작업(프로세스 또는 스레드)을 빠르게 전환하면서 마치 동시에 처리하는 것처럼 보이게 해야 하기 때문에 멀티태스킹 환경에서 필수적이다.
❓ 컨텍스트 스위칭은 언제 일어나나요?
- 타이머 인터럽트 : 주어진 Time Slice(Time Quantum)을 다 사용했을 때
- 시스템 콜 : 커널 모드로 전환되면서 다른 작업으로 CPU가 넘어갈 필요가 있을 때
- I/O 인터럽트 : 실행 중인 프로세스가 I/O 작업 대기로 전환되어 CPU 사용을 멈출 때
- 우선순위 스케줄링 : 더 높은 우선순위를 가진 프로세스가 Ready 상태가 되어 CPU를 선점할 때
- 사용자 요청 :
sched_yield() 또는 명시적인 sleep() 호출 시
❓ 컨텍스트 스위칭 시 어떤 일이 일어나나요?
1. 현재 작업의 상태 저장
- CPU가 현재 작업(프로세스 또는 스레드)을 중단하기 전에, 그 작업의 모든 상태(프로그램 카운터, 레지스터 값 등)를 저장한다.
- 이 정보는 보통 PCB(Process Control Block) 또는 TCB(Thread Control Block)라는 메모리 구조에 저장된다.
2. 다음 작업의 상태 복원
- CPU는 대기 중인 다른 작업의 상태를 PCB 또는 TCB에서 불러와 복원한다. 이 때, CPU는 이전 작업이 어디에서 중단되었는지, 어떤 값을 가지고 있었는지 알 수 있다.
3. 새로운 작업 실행
- CPU는 복원된 상태에서 다음 작업을 이어서 실행한다. 이 작업이 끝나거나 일정 시간 동안 실행된 후, 다시 다른 작업으로 전환된다.
이 과정이 매우 빠르게 반복되면서 여러 작업이 동시에 실행되는 것처럼 보이게 된다.
❓ 프로세스 vs 스레드 컨텍스트 스위칭 차이
| 항목 | 프로세스 전환 | 스레드 전환 |
|---|
| 비용 | 높음 | 낮음 |
| 주소 공간 | 주소 공간 전체 전환 (페이지 테이블 변경) | 동일한 주소 공간 공유 (공유 메모리 사용) |
| 캐시 영향 | ✅ 캐시 flush 가능 | ❌ 캐시 보존 가능 |
| 메모리 변경 | PCB + 메모리 구조 전환 | TCB만 바꾸면 됨 |
| 속도 | 느림 | 빠름 |
주소 공간
- 스레드는 주소 공간(코드, 데이터, 힙 스택 등)을 공유한다. 즉, 페이지 테이블도 같고, 메모리 구조 전체가 같다.
- 각 스레드는 별도의 스택 영역을 가지는데, 이로 인해 스레드 전환 시 주소 공간이 바뀌는 게 아니라 스택 포인터(스택 위치)만 바뀌는 것이다.
- 프로세스는 컨텍스트 스위칭 시 다른 프로세스의 주소 공간으로 완전히 바꿔야 한다.
- MMU(Memory Management Unit)와 TLB(Translation Lookaside Buffer)도 관리해주어 메모리 주소 관련된 처리를 추가적으로 수행해야 한다.
- 스레드 간의 스위칭에서는 실행되고 있던 스레드의 상태를 TCB에 저장하고, 새로 실행될 스레드의 상태를 로딩하는 것으로 해결이 되지만, 프로세스 간 스위칭에서는 MMU가 실행 될 작업의 메모리를 보도록 해야 하고 캐시 역할을 하는 TLB를 완전히 비워줘야 한다.
❓ 컨텍스트는 커널 스택에 어떻게 저장되나요?
1. CPU 레지스터 상태
- 프로그램 카운터(PC), 스택 포인터(SP), 범용 레지스터 값 등
2. 프로세스 상태
3. 메모리 관리 정보
- 페이지 테이블, 세그먼트 테이블 등 메모리 매핑 관련 정보
4. I/O 상태 정보
- 열려 있는 파일, 사용 중인 I/O 장치의 상태 정보
5. 스케줄링 정보
- 우선순위, CPU 사용 시간 등 스케줄링 결정에 필요한 정보
🕐 프로세스 스케줄링 알고리즘
스케줄링 알고리즘 종류
1. FCFS (First-Come, First-Served)
- 비선점 스케줄링으로, 먼저 도착한 프로세스 먼저 실행하는 스케줄링 알고리즘
- 짧은 작업이 긴 작업을 기다리는 경우가 발생할 수 있다.
- Convoy Effect : 실행 시간이 짧은 프로세스들이 실행 시간이 긴 프로세스를 계속해서 기다리면서 효율성이 저하된다.
2. SJF (Shortest Job First)
- 비선점 스케줄링으로, 실행시간이 가장 짧은 프로세스부터 실행
- 이미 실행 시간이 긴 프로세스가 실행 중이라면, 새로 도착한 짧은 프로세스는 기다리긴 해야 한다.
- Starvation (기아 상태) : 실행 시간이 긴 프로세스가 영원히 CPU를 할당받을 수 없게 된다.
3. SRTF (Shortest Remaining Time First)
- 선점형 스케줄링으로, 새로운 프로세스가 들어온 시점에서 현재까지 남은 실행 시간이 가장 적은 프로세스를 먼저 실행
- Starvation : SJF와 동일한 이유로 실행 시간이 긴 프로세스가 영원히 CPU를 할당받을 수 없게 된다.
4. Priority Scheduling
- 우선순위가 높은 프로세스가 CPU를 선점하도록 하는 스케줄링
- 선점, 비선점 스케줄링 방식 모두 사용 가능
- 선점형 : 더 높은 우선순위의 프로세스가 도착하면 현재 실행 중인 프로세스에게서 CPU를 뺏는다.
- 비선점형 : 더 높은 우선순위의 프로세스가 도착하면 Ready Queue에 넣고 바로 다음에 실행되게 한다.
- 무기한 봉쇄(Indefinite blocking) : 우선순위가 높은 프로세스가 Blocking 되어있어 CPU가 계속해서 대기해야 하는 상황
5. Round Robin
- 선점형 스케줄링으로, 각 프로세스에 일정 시간 (Time Slice, Time Quantum)씩 순서대로 CPU를 할당해주는 방식
- CPU를 할당받고 시간이 지나면 Ready 상태로 돌아가 Ready Queue의 Tail로 들어간다.
❓ RR을 사용할 때, Time Slice에 따른 trade-off
- 설정한 Time Quantum이 너무 작아지면 Context Switching으로 인한 오버헤드가 증가한다.
- 반대로 너무 커지면, 오버헤드는 감소하지만 응답 시간이 느려지고, 대화형 작업에 불리하다.
6. Multi-level Queue Scheduling
- 프로세스들을 우선순위나 유형에 따라 여러 큐로 분류하고 각 큐마다 다른 스케줄링을 수행하는 방식
- 다양한 작업 유형의 처리가 가능해지지만, 프로세스가 하나의 큐에 영구적으로 할당되는 방식이기 때문에 상황에 맞게 큐를 갈아탈 수 없다.
7. Multi-level Feedback Queue Scheduling
- 다단계 큐에서 작업의 동작에 따라 큐 이동이 가능, 우선 순위를 동적으로 조정 가능
- CPU를 사용하고 난 프로세스는 한단계 낮은 큐로 다시 삽입된다.
-> 우선순위가 낮아져도 커널 프로세스가 일반 프로세스 큐에 삽입되지 않음
- 우선순위에 따라 Time Slice 크기가 다르다. (우선순위가 낮을수록 Time Slice 크기 증가)
- 효율적이고 적응적인 스케줄링이 가능하지만, 구현이 어렵다.
❓ 싱글 스레드 CPU 에서 상시로 돌아가야 하는 프로세스가 있다면, 어떤 스케줄링 알고리즘을 사용하는 것이 좋을까?
- 상시 돌아가야 하는 프로세스는 실시간 시스템에서 흔히 볼 수 있으며 실시간 스케줄링 알고리즘이 적합하다.
1. Rate Monotonic Scheduling (RMS)
- 고정 우선순위 스케줄링 알고리즘으로 주기가 짧은 작업(주기적으로 반복적으로 실행되는 작업)에 높은 우선순위를 부여하는 방식
- 실시간 시스템에서 주기가 짧을수록 더 자주 CPU를 사용해야 하므로, 주기가 짧은 작업이 높은 우선순위를 가져가야 한다는 가정에서 설계
- 고정된 우선순위를 사용하므로, 작업이 시작될 때부터 우선순위가 고정된다.
2. Earliest Deadline First (EDF)
- 동적 우선순위 스케줄링 알고리즘으로, 기한이 가장 빠른 작업에 우선순위를 부여하여 실행하는 방식
- 주어진 시간 내에 반드시 완료되어야 하는 작업이 있는 경우, 해당 작업이 다른 작업보다 우선적으로 처리된다.
3. Preemptive Priority Scheduling
- 우선순위 기반의 선점형 스케줄링 알고리즘으로 우선순위가 높은 프로세스가 CPU를 선점하여, 항상 우선순위 높은 프로세스가 먼저 실행되도록 보장한다.
- 특정 프로세스가 상시로 돌아가야 하는 경우에는 해당 프로세스에 가장 높은 우선순위를 부여하여, 다른 작업보다 항상 우선적으로 CPU 자원을 사용할 수 있도록 한다.
- 이 방식은 실시간 시스템에서 중요한 작업이 지연없이 실행되도록 보장하는 데 유용하다.
동시성 vs 병렬성
동시성 (Concurrency)
동시성은 하나의 시스템이 여러 작업을 동시에 처리하는 것처럼 보이게 하는 것이다.
병렬성 (Parallelism)
병렬성은 여러 CPU 또는 코어에서 동시에 여러 작업이 실제로 동시에 실행되는 것이다.
동시성, 병렬성 비교
| 구분 | 동시성 | 병렬성 |
|---|
| 개념 | 동시에 처리하는 것처럼 보이게 하는 것 | 여러 작업을 실제로 동시에 처리하는 것 |
| 사용 코어 수 | 싱글 코어 | 멀티 코어 |
| 동작 방식 | 싱글 코어에서 멀티 쓰레드(Multi thread)를 동작 시키는 방식 | 멀티 코어에서 멀티 쓰레드(Multi thread)를 동작시키는 방식 |
| 개념적 차이 | 논리적인 개념 | 물리적인 개념 |
❓ Multi-level Feedback Queue는 어떤 문제점들을 해결한다고 볼 수 있을까요?
다른 스케줄링 알고리즘에서의 문제점
- SJF, SRTF : 짧은 실행 시간을 가진 작업에 유리하지만, 실행 시간이 긴 작업은 영원히 대기해야 하는 문제 발생
- Fixed Priority : 우선순위가 고정되어있기 때문에 우선순위가 낮은 프로세스는 영원히 실행되지 못할 수 있다.
- Multi-Level Queue : 큐 간 프로세스 이동이 불가능해 동적으로 우선순위를 변경해줄 수 없다.
MLFQ의 개선 방법
- 짧거나 우선순위가 높은 작업은 우선 순위 큐에서 빠르게 처리되어 우선 순위에 따른 작업 처리가 가능하다.
- 오래 대기한 작업은 점차 높은 우선순위 큐로 이동(Aging)하여 기아 문제를 해결한다.
- 프로세스의 실행 시간에 따라 우선순위가 동적으로 변동되어, 자주 실행되지 않는 프로세스도 처리할 수 있다.
❓ FIFO 스케줄러는 어떤 시나리오에 사용하면 좋을까?
- FIFO 스케줄러는 구현이 간단하며, 운영체제가 간단한 환경에서 동작할 때 활용
- 프로세스의 우선순위가 중요하지 않은 경우
- 프로세스의 실행 시간이 모두 비슷하고, 도착한 순서대로 처리해도 문제가 없을 경우
❓ 스레드 스케줄링
스레드 스케줄링은 운영 체제가 프로세스 내의 스레드들 사이에서 CPU 시간을 어떻게 분배할지 결정하는 과정이다.
- 스레드의 스케줄링은 프로세스의 스케줄링과 유사하지만, 스레드는 같은 프로세스의 주소 공간을 공유하므로 메모리 전환 작업이 필요하지 않다는 점이 다르다.
❓ 유저 스레드와 커널 스레드의 스케줄링 알고리즘은 똑같나요?
스레드는 유저 스레드와 커널 스레드로 구분될 수 있다.
유저 스레드 (사용자 수준 스레드)
- 사용자 수준 스레드의 스케줄링은 애플리케이션(라이브러리)에 의해 관리되며, 운영 체제는 이러한 스레드의 존재를 인식하지 못한다.
- 커널이 아닌 사용자 수준에서 이루어지기 때문에 더 빠르게 이루어질 수 있다.
커널 수준 스레드
- 커널 수준 스레드의 스케줄링은 운영 체제에 의해 직접 관리되며, 운영 체제는 각 스레드에 CPU 시간을 할당한다.
- 시스템 레벨에서 실행되기 때문에 보다 높은 권한과 자원에 대한 접근을 갖는다.
유저 스레드와 커널 스레드 간의 스케줄링 알고리즘은 서로 다를 수 있다.
🔐 뮤텍스와 세마포어
프로세스 간 메세지를 전송하거나, 공유 메모리를 통해 공유된 자원에 여러 개의 프로세스가 동시에 접근하면 Critical Section 문제가 발생할 수 있다. 이를 해결하기 위해 데이터를 한 번에 하나의 프로세스만 접근할 수 있도록 제한을 두는 동기화 방식을 취해야 한다. 동기화 도구에는 대표적으로 뮤텍스(Mutex)와 세마포어(Semaphore)가 있다. 이들은 모두 공유된 자원의 데이터를 여러 스레드/프로세스가 접근하는 것을 막는 역할을 한다.
1️⃣ 뮤텍스
공유된 자원의 데이터 혹은 임계 영역(Critical Section) 등에 하나의 Process 혹은 Thread가 접근하는 것을 막아준다. (동기화 대상이 하나)
- 임계 구역(Critical Section)을 가진 스레드들의 실행 시간이 서로 겹치지 않고 각각 단독으로 실행(상호 배제)되도록 하는 기술이다.
- 한 프로세스에 의해 소유될 수 있는 Key를 기반으로 한 상호배제 기법이고, Key에 해당하는 어떤 객체가 있으며, 이 객체를 소유한 프로세스/스레드만이 공유 자원에 접근할 수 있다.
- 다중 프로세스들의 공유 자원에 대한 접근을 조율하기 위해 동기화 또는 락을 사용함으로써 뮤텍스 객체를 두 스레드가 동시에 사용할 수 없다.
2️⃣ 세마포어
공유된 자원의 데이터 혹은 임계 영역 등에 여러 Process 혹은 Thread가 접근하는 것을 막아준다. (동기화 대상이 하나 이상)
- 사용하고 있는 스레드/프로세스의 수를 공통으로 관리하는 하나의 값을 이용해 상호배제를 달성한다.
- 공유 자원에 접근할 수 있는 프로세스의 최대 허용치만큼 동시에 사용자가 접근할 수 있으며, 각 프로세스는 세마포어의 값을 확인하고 변경할 수 있다.
- 일반적으로 비교적 긴 시간을 확보하는 리소스에 대해 사용된다.
예) DB 연결 풀, 네트워크 소켓, 프린터, 스레드 풀 등
🎯 뮤텍스 vs 세마포어
| 구분 | 뮤텍스 (Mutex) | 세마포어 (Semaphore) |
|---|
| 목적 | 상호 배제를 위해 (Mutual Exclusion) | 동기화 및 제한된 자원 접근 제어 |
| 값의 범위 | 0 또는 1 (락 or 언락) | 0 이상 (공유 자원 개수 / 허용 가능한 최대 동시 접근 수) |
| 소유 개념 | 있음 → 락을 건 스레드만 락 해제 가능 | 없음 → 누구든 해제 가능 |
| 사용 예시 | 임계 구역 보호 | 리소스 수 제한 (예: 연결 풀, 프린터 등) |
| 구조 | 락/언락에 집중 | 카운팅에 집중 (자원 개수 관리) |
❓ 이진 세마포어 vs 뮤텍스
이진 세마포어
- 이진 세마포어는 값을 0과 1만 가지는 세마포어로, 상호 배제를 위해 신호 전달 매커니즘을 사용해서 잠금을 구현한 것이다.
- 세마포어가 0이면 잠겨있는 것, 1이면 잠금 해제
| 바이너리 세마포어 | 뮤텍스 |
|---|
| 신호 전달 메커니즘 기반으로 동작 | 잠금 메커니즘 기반으로 동작 |
| 현재 스레드보다 우선순위가 높은 스레드가 바이너리 세마포어를 해제하고 잠글 수 있음 | 뮤텍스를 획득한 스레드는 크리티컬 섹션에서 나갈 때만 뮤텍스 해제 가능 |
값은 wait() , signal() 에 따라 변경 | 값이 locked, unlocked 으로 수정 |
| 소유권이 없다 | 뮤텍스를 소유한 스레드만 잠금을 해제 가능하므로 소유권이 있음 |
❓ Spin Lock
Race Condition 상황에서 Lock이 반환될 때까지, 즉 Critical Section에 진입이 가능할 때까지 프로세스가 재시도하며 대기하는 상태
즉, 스레드가 락을 얻을 때까지 무한 루프를 돌며 확인하는 동기화 매커니즘
- Spin Lock을 사용하는 스레드는 락을 못 잡았다고 해서 곧바로 대기 상태 (Blocked)로 바뀌지 않고, Ready 상태에서 CPU를 계속 점유하며 루프를 돈다.
즉, mutex.lock() → 못 잡으면 sleep/block → 커널에 요청 → 컨텍스트 스위칭 발생
spin_lock() → 못 잡으면 while 루프 돌며 계속 검사 → 컨텍스트 스위칭 X
장점
스핀락은 스레드가 대기 상태로 전환되지 않기 때문에 컨텍스트 스위칭이 일어나지 않는 특징이 있다.
- 컨텍스트 스위칭에 필요한 CPU의 오버헤드를 줄일 수 있다.
- 락의 획득이 빠르다.
단점
- Busy Waiting : 무한 루프를 돌면서 CPU를 계속 사용하기 때문에 스핀 락의 획득을 위해 CPU의 오버헤드가 발생할 수 있다.
- Starvation : 특정 스레드나 프로세스가 공유 자원을 오랫동안 점유하면, 다른 스레드들이 대기 상태(기아 상태)에 갇힐 수 있다.
🛠 단점 해결법
- Spin + Sleep
sleep_for 이나 yield 함수를 사용하여 스레드가 일정 시간동안 대기하거나 자발적으로 CPU를 양보하는 방법을 제공할 수 있다. 이를 통해 다른 스레드가 lock을 얻을 수 있는 기회를 제공하고, CPU 사용률을 줄일 수 있다.
💡 정리
- 스핀락은 스레드나 프로세스의 race condition이 짧을 때 유용하다.
- 스핀락의 무한 루프 덕분에 락의 획득과 반환이 빨라 실행 속도도 빨라지고, 컨텍스트 스위칭이 생략되기 때문에 CPU의 오버헤드도 줄어든다.
- 스핀락은 여러 개의 CPU 코어가 존재할 때 유용하다.
- 사용하지 않는 CPU 코어에서 스핀락을 통해 대기하다가 바로 락을 획득할 수 있다.
❓ 시스템 콜 기반의 락
뮤텍스와 세마포어 모두 커널이 관리하기 때문에, 락을 얻거나 해제할 때 시스템 콜이 발생한다.
장점
- OS가 정확한 락 상태를 추적할 수 있게 된다.
- 우선순위 조정, 데드락 방지 등 부가 기능 지원 가능
- **멀티 프로세스 간 공유 락 구현 가능
단점
- 시스템 콜은 유저모드 -> 커널모드 전환이 필요하고, 여기에 드는 비용이 크다.
- 빈번한 락/언락 요청은 성능을 저하시킬 수 있다.
🛠 단점 해결법
- 유저 모드 락 (User-space lock) + 커널 락 혼합
futex (Fast User-space Mutex)로, 보통은 유저 공간에서 Spin Lock처럼 대기하다가, 충돌이 심할 때만 커널에 시스템 콜로 진입하는 방식이다.
이렇게 하면, 빠른 락 획득 시 불필요한 시스템 콜 비용을 줄일 수 있고, 락 대기가 길어지면 커널이 관리해서 효율적으로 스케줄링해줄 수 있게 된다.