탐색으로 돌아가기
Operating System6 / 24 단계

Thread

스레드와 컨텍스트 스위칭

한 프로세스는 여러 스레드를 가질 수 있고, 이들은 같은 메모리를 공유해 가볍게 병렬 작업을 한다.

스레드(Thread)는 프로세스 내부의 실행 흐름 단위로, CPU가 스케줄링하는 최소 단위다. 하나의 프로세스는 여러 스레드를 가질 수 있으며, 이 스레드들은 프로세스의 코드(text)·전역 데이터(data)·힙(heap)·열린 파일 디스크립터를 공유하는 대신, 각자 고유한 스택(stack)·레지스터·프로그램 카운터(PC)·스레드 로컬 저장소(TLS)를 가진다. 커널은 각 스레드를 스레드 제어 블록(TCB, Thread Control Block)으로 관리하며 여기에는 스레드 ID·상태·PC·레지스터·스택 포인터·우선순위·소속 PCB 포인터가 담긴다. 스레드는 구현 위치에 따라 커널이 모르는 사용자 수준 스레드(user-level thread)와 커널이 스케줄링하는 커널 수준 스레드(kernel-level thread)로 나뉘고, 이 둘의 매핑 관계에 따라 N:1(다대일), 1:1(일대일), M:N(다대다) 모델로 분류된다. 컨텍스트 스위칭(context switching)은 실행 중인 스레드의 문맥(레지스터·PC 등)을 그 TCB에 저장하고 다음 스레드의 문맥을 TCB에서 복원하는 과정인데, 같은 프로세스 내 스레드 간 전환은 가상 주소 공간이 바뀌지 않아 TLB 플러시를 피하므로 프로세스 간 전환보다 훨씬 저렴하다. 여러 스레드가 공유 자원에 동시에 접근하면 실행 순서에 따라 결과가 달라지는 경쟁 상태(race condition)가 발생하므로, 임계 구역(critical section)을 뮤텍스(mutex)·세마포어(semaphore) 같은 동기화 기법으로 보호해야 한다.

내부 구성

TCB - 스레드 ID (Thread ID)
OS가 스레드에 부여하는 유일 식별자다
TCB - 스레드 상태 (Thread State)
running/ready/waiting 등 현재 실행 상태를 기록해 스케줄러가 사용한다
TCB - 프로그램 카운터 (PC)
스레드가 다음에 실행할 명령의 주소를 가리켜 중단 지점 재개를 가능케 한다
TCB - 레지스터 집합 (Registers)
컨텍스트 스위칭 시 저장/복원되는 스레드별 CPU 레지스터 내용이다
TCB - 스택 포인터 (Stack Pointer)
스레드 전용 스택의 현재 최상단을 가리킨다
TCB - 우선순위 (Priority)
스케줄러가 다음 실행 스레드를 고를 때 참조하는 스케줄링 우선순위다
TCB - PCB 포인터
스레드가 속한 프로세스(PCB)를 연결해 공유 자원(주소 공간·파일)에 접근하게 한다
공유 자원 - Text(코드)
같은 프로세스의 모든 스레드가 동일한 프로그램 명령을 공유 실행한다
공유 자원 - Data(전역/정적)
전역·정적 변수를 모든 스레드가 공유하므로 동기화 없이 접근 시 race condition의 근원이 된다
공유 자원 - Heap
동적 할당 메모리를 스레드들이 공유해 데이터 공유·통신에 활용한다
공유 자원 - 열린 파일 디스크립터
프로세스가 연 파일·소켓 핸들을 모든 스레드가 공유한다
스레드별 자원 - Stack
각 스레드가 독립된 호출 프레임·지역 변수를 가지도록 전용 스택을 보유한다
스레드별 자원 - 레지스터 & PC
각 스레드가 독립적인 실행 문맥을 유지하도록 별도의 레지스터·PC를 가진다
스레드별 자원 - TLS(Thread-Local Storage)
스레드마다 격리된 전역 변수 저장 공간으로 errno 등에 쓰인다
사용자 수준 스레드 (User-level Thread)
커널이 모르는 라이브러리 수준 스레드; 전환이 매우 빠르나 하나의 블로킹 시스템 콜이 프로세스 전체를 멈춘다
커널 수준 스레드 (Kernel-level Thread)
커널이 직접 스케줄링해 멀티프로세서에서 진짜 병렬 실행이 가능하고 한 스레드 블로킹이 다른 스레드를 막지 않는다
뮤텍스 (Mutex)
한 번에 하나의 스레드만 임계 구역에 들어가게 하는 상호 배제 잠금으로 소유권(ownership) 개념이 있다
세마포어 (Semaphore)
정수 카운터로 동시 접근 가능한 자원 수를 제어한다 (counting/binary), P(wait)·V(signal) 연산 사용

핵심 포인트

  • 스레드는 프로세스 내 실행 흐름 단위이자 CPU 스케줄링의 최소 단위다
  • 공유 자원: 코드(text)·데이터(data)·힙(heap)·열린 파일 / 스레드별 자원: 스택·레지스터·PC·TLS
  • 커널은 각 스레드를 TCB(ID·상태·PC·레지스터·스택포인터·우선순위·PCB포인터)로 관리한다
  • 사용자 수준 스레드(빠르지만 블로킹 취약) vs 커널 수준 스레드(진짜 병렬 가능)
  • 매핑 모델: N:1(다대일), 1:1(일대일, 현대 주류), M:N(다대다 하이브리드)
  • 스레드 컨텍스트 스위칭은 주소 공간 유지로 TLB 플러시가 없어 프로세스 전환보다 저렴하다
  • 공유 자원 동시 접근 시 race condition 발생 → 뮤텍스/세마포어로 임계 구역 보호

심화

면접에서 반드시 나오는 것이 '뮤텍스 vs 세마포어' 구분이다. 뮤텍스는 잠근 스레드만이 풀 수 있는 소유권 개념이 있어 상호 배제(1개 자원)에 쓰이고, 세마포어는 소유권이 없어 어느 스레드나 signal할 수 있으며 N개의 동일 자원 풀 관리나 스레드 간 신호(binary semaphore)에 쓰인다. 또한 race condition은 단순히 '동시 접근'이 아니라 'read-modify-write' 같은 비원자적(non-atomic) 연산이 컨텍스트 스위칭으로 인터리빙될 때 발생한다는 점(예: counter++는 load·add·store 3단계)을 정확히 설명해야 한다. 한 단계 더 깊은 논점은 스레딩 모델의 역사적 변천이다. 초기 M:N 모델(Solaris, 초기 Linux NGPT)은 사용자 공간 스케줄러와 커널 스케줄러가 이중으로 존재해 구현이 복잡하고 성능이 오히려 나빠, 오늘날 Linux(NPTL)와 Windows는 1:1 모델을 표준으로 채택했다. 대신 M:N의 장점(가벼운 대량 스레드)은 언어 런타임 수준의 그린 스레드/코루틴으로 부활했는데, Go의 goroutine(G-M-P 스케줄러)과 Java 21의 Virtual Thread(Project Loom)가 대표적으로 M:N을 사용자 공간에서 재현한 사례다. 마지막으로 동기화의 함정으로 데드락(deadlock)의 4가지 필요조건(상호 배제·점유와 대기·비선점·순환 대기)과 이를 깨는 방법, 그리고 우선순위 역전(priority inversion) 문제까지 언급할 수 있으면 시니어 수준의 답변이 된다.

쉽게 말하면 한 세대(프로세스) 안의 가족(스레드)이 같은 냉장고(메모리)를 공유하며 각자 일하는 것.

면접 예상 질문

#스레드#컨텍스트 스위칭#동시성#공유 메모리