스레드: 사용자·커널 수준 스레드와 Thread Pool, Fork-Join
스레드의 개념과 TCB, 사용자 수준과 커널 수준 스레드의 차이와 매핑 모델, Thread Pool과 Fork-Join을 정리한다.
시리즈 · 운영체제6 / 18
- 운영체제의 유형
- System Call과 Dual Mode
- 인터럽트와 폴링
- 프로세스와 PCB, 프로세스의 상태 변화
- 프로세스 주소 공간과 Stack이 높은 주소부터 할당되는 이유
- 스레드: 사용자·커널 수준 스레드와 Thread Pool, Fork-Join
- Context Switching
- 프로세스 스케줄링: 단계와 알고리즘
- Thread Scheduling: 경합 범위 PCS와 SCS
- 경쟁 상태와 상호 배제: Thread Safe, 데커·피터슨 알고리즘
- 뮤텍스와 세마포, 모니터
- 교착 상태: 발생 조건과 해결 방법
- 메모리 계층 구조와 캐시 메모리
- 메모리 관리: 주소 바인딩과 연속 메모리 할당
- 가상 메모리: Paging과 Segmentation, TLB
- 요구 페이징과 페이지 교체 알고리즘, Thrashing
- File Descriptor와 File System, I-Node
- 동기/비동기와 블로킹/논블로킹, I/O Multiplexing
스레드
- 프로세스의 두 특성인 자원과 제어 중 제어만 분리한 실행 단위다. 프로세스 내부의 작업 흐름이라고 할 수 있다.
- 프로세서 활용의 기본 단위다. 프로세스는 최소 하나의 스레드를 가지며 스케줄링은 스레드 단위로 이루어진다.
- 같은 프로세스의 스레드들은 프로세스 관리 정보와 주소 공간을 공유한다. Stack만 스레드마다 따로 가지고, Code·Data·Heap은 공유한다. (프로세스 주소 공간 참고)

스레드를 쓰는 이점
- 사용자 응답성 증가: 일부 스레드가 지연되어도 다른 스레드가 작업을 처리할 수 있다.
- 자원 공유: 프로세스의 자원과 메모리를 공유하므로 커널 개입을 피할 수 있다.
- 경제성: 프로세스 간 문맥 교환보다 스레드 전환이 효율적이다.
- 다중 처리로 성능과 효율이 향상된다.
스레드와 프로세스의 관계

다중 스레드는 프로그램 하나를 여러 실행 단위로 쪼개어 실행한다는 점에서 다중 처리와 비슷하다.
스레드 제어 블록 (TCB)
프로세스에 PCB가 있듯이 스레드는 TCB(Thread Control Block)를 가진다. PCB는 TCB의 리스트를 가지며, 같은 프로세스의 스레드끼리는 서로 보호되지 않는다. TCB의 내용은 다음과 같다.
- 실행 상태: 프로세서 레지스터, 프로그램 카운터, 스택 포인터
- 스케줄링 정보: 상태, 우선순위, 프로세서 시간
- 계정 정보
- 스케줄링 큐용 포인터
- 자신이 속한 PCB를 가리키는 포인터
사용자 수준 스레드
- 프로세스 안의 스레드 라이브러리가 스레드를 관리하며, 스레드와 관련된 모든 행위를 사용자 영역에서 처리한다.
- 커널은 사용자 수준 스레드의 존재를 알지 못하고, 프로세스를 한 단위로 인식해 프로세서를 할당한다.

장점은 커널의 개입을 받지 않는 데서 나온다.
- 낮은 오버헤드: 스케줄링이나 동기화를 위해 커널을 호출하지 않으므로 모드 전환이 없다.
- 이식성: 커널에 독립적으로 스케줄링할 수 있어 모든 운영체제에 적용할 수 있다.
- 유연한 스케줄링이 가능하다.
단점은 다음과 같다.
- 시스템의 동시성을 지원하지 않는다. 커널은 프로세스 단위로 자원을 할당하므로, I/O 등으로 스레드 하나가 block 되면 같은 프로세스의 다른 스레드도 실행할 수 없다.
- 멀티 프로세서를 활용할 수 없다.
- 시스템 전반에 걸친 스케줄링 우선순위를 지원하지 않는다. 스레드 생성 시 우선순위를 정할 수는 있지만, 어떤 스레드가 먼저 동작할지 정확히 보장되지 않는다.
- 스레드 간 보호가 불가능하다.
커널 수준 스레드
- 스케줄링의 주체가 커널이다. 커널이 모든 프로세스와 스레드를 직접 관리한다.
- 스레드 하나가 block 되어도 같은 프로세스의 다른 스레드는 계속 작업한다.
장점은 다음과 같다.
- 한 프로세스의 스레드들을 여러 프로세서에 동시에 디스패치할 수 있어 멀티 프로세서 환경에서 빠르게 동작한다.
- 커널이 직접 스레드를 제공하므로 안정성이 높고 기능이 다양하다.
단점은 다음과 같다.
- 스레드의 생성, 스케줄링, 동기화를 위해 커널을 호출해야 해서 무겁고 문맥 교환 부하가 크다.
- 사용자 모드와 커널 모드 사이의 전환이 빈번해 성능 저하가 발생한다.
매핑 모델
사용자 스레드와 커널 스레드를 어떻게 연결하느냐에 따라 나뉜다.
- Many-To-One (n:1): 커널 스레드 하나가 여러 사용자 스레드를 담당한다. 사용자 수준 스레드가 이 방식이다.
- One-To-One (1:1): 커널 스레드 하나가 사용자 스레드 하나를 담당한다. 스레드 하나가 block 되어도 다른 스레드를 실행할 수 있지만, 스레드마다 커널 스레드를 생성해야 하므로 오버헤드가 크다. 리눅스가 이 모델을 사용한다.
- Many-To-Many (n:m): 여러 커널 스레드가 여러 사용자 스레드를 담당한다.
Many-To-Many와 One-To-One을 섞어 필요에 따라 유연하게 동작하는 혼합형 모델도 있다. 효율과 유연성을 함께 얻는 방식이다.

커널이 스레드를 어떤 범위에서 스케줄링하는지는 Thread Scheduling에서 이어서 다룬다.
Thread Pool
작업 처리에 사용할 스레드를 제한된 개수만큼 미리 만들어 두고, 작업 큐(Queue)에 들어오는 작업을 스레드가 하나씩 맡아 처리하는 방식이다. Java에서는 Executors, ExecutorService로 사용한다.

주의할 점은 다음과 같다.
- 스레드를 너무 많이 만들어 두면, 아무 일도 하지 않으면서 메모리만 차지하는 스레드가 생긴다.
- 특정 스레드에만 일이 몰리면 스레드마다 작업량이 달라 비효율적으로 동작한다.
Fork-Join
하나의 작업을 여러 개의 Sub Task로 분리(Fork)해 각각 처리하고, 그 결과를 합쳐(Join) 최종 결과를 만드는 방식이다. Java는 ForkJoinPool로 지원한다.

Thread Pool의 두 번째 문제, 즉 한 스레드의 일이 너무 많고 다른 스레드는 여유로울 때 일을 재분배하는 방법으로 볼 수 있다.
- 장점: 한 스레드에만 일이 몰릴 때 효과적이다.
- 단점: 작업이 처음부터 스레드들에 고르게 나뉘는 경우에는 분할과 병합의 오버헤드만 생기므로, 그냥 실행하는 편이 더 효율적이다.
참고
- 운영체제: 그림으로 배우는 구조와 원리 (한빛아카데미)
- hamait.tistory.com/612