프로세스 안에서 실행 흐름을 나누는 쓰레드
쓰레드의 실행 단위와 메모리 공유 구조, 커널·사용자 쓰레드 모델, 동기화와 스케줄링 특성을 정리한다.
2026-08-14 · 최초 발행 2025-12-30
프로세스 내부의 실행 흐름
쓰레드(Thread)는 프로세스에 속하면서 CPU가 처리하는 기본 작업 단위를 이루는 실행 흐름이다. 하나의 프로세스에는 여러 쓰레드가 존재할 수 있으며, 각 쓰레드는 필요한 일부 자원만 따로 보유하고 나머지 자원은 프로세스와 함께 사용한다.
이 특성 때문에 쓰레드는 Light Weight Process, 즉 경량 프로세스로도 불린다. 프로세스가 Heavy Weight라면 쓰레드는 더 가벼운 실행 단위에 해당한다. 생성 속도는 쓰레드가 10-100배 빠르며, 생성과 종료, Context Switch에 드는 비용도 낮다.
전환 비용과 CPU 활용이 달라지는 지점
프로세스 사이의 Context Switch에서는 전체 메모리 맵 교체, TLB 플러시, 캐시 무효화가 일어나며 수십 마이크로초가 소요된다. 반면 쓰레드 간 전환은 레지스터와 스택을 교체하고 메모리 맵을 유지하며, 캐시도 일부 유지된다. 소요 시간은 수 마이크로초로, Context Switch 시간은 10-100배 감소한다.
작업 전환 비용이 작아지면 빈번한 전환이 가능해지고 응답성도 높아진다. 멀티코어 환경에서는 서로 다른 쓰레드를 각각의 코어에서 실행해 진정한 병렬 처리를 수행할 수 있다. 한 쓰레드가 I/O를 기다리는 동안 다른 쓰레드가 CPU를 사용하므로 유휴 시간을 줄이는 데도 쓰인다.
단일 쓰레드: [작업1] [I/O 대기] [작업2] [I/O 대기]
멀티 쓰레드: [작업1] + [작업2] (I/O 대기 중 다른 작업 수행)
다만 쓰레드의 실행 순서는 미리 정해지지 않는다. OS 스케줄러가 동적으로 순서를 결정하므로, 공유 자원에 접근하는 코드에는 동기화가 필요하다. 그렇지 않으면 Race Condition이 생길 수 있고, 여러 잠금이 순환 대기하면 데드락으로 이어질 수 있다.
관리 주체에 따른 쓰레드 모델
커널 쓰레드(Kernel Thread)는 커널이 존재를 인지하고 OS가 직접 관리하는 방식이다. 시스템 콜로 생성·관리되며, 커널 스케줄러가 쓰레드 단위로 스케줄링한다. 멀티코어에서 병렬 실행할 수 있고 한 쓰레드가 블록되어도 다른 쓰레드는 계속 실행할 수 있다.
장점은 멀티코어 활용, 높은 안정성, 공정한 스케줄링이다. 반대로 생성과 관리 비용, 시스템 콜 오버헤드, 쓰레드 수 제한이 부담이 된다. POSIX 쓰레드(pthread), Windows 쓰레드, Linux Native POSIX Thread Library(NPTL)가 예시다.
사용자 쓰레드(User Thread)는 커널이 쓰레드의 존재를 알지 못하고 라이브러리가 사용자 공간에서 관리한다. 커널은 프로세스 단위로만 인식하며, 라이브러리 스케줄러가 실행을 조정한다. 빠른 생성과 전환, 시스템 콜 불필요, 쓰레드 수 제한 없음, 높은 이식성이 장점이다.
그러나 멀티코어 활용에는 제한이 있고, 한 쓰레드가 블록되면 전체 프로세스도 블록된다. 스케줄링 공정성도 낮다. Green Thread(Java 초기), GNU Portable Threads, Fiber(Windows)가 여기에 속한다.
하이브리드 모델은 사용자 쓰레드와 커널 쓰레드를 결합한 구조다. M:N 모델에서는 M개 사용자 쓰레드가 N개 커널 쓰레드에 연결된다. 사용자 쓰레드의 유연성과 커널 쓰레드의 멀티코어 활용을 함께 가져가 최적의 성능을 목표로 한다. Solaris 쓰레드(과거), Go 언어의 Goroutine, Erlang 프로세스가 예시다.
공유하는 자원과 분리되는 자원
쓰레드는 Code(Text), Data, Heap 영역을 공유한다. 실행 코드와 전역 변수, Static 변수, malloc(), new로 할당한 동적 메모리가 여기에 포함된다. 열린 파일인 파일 디스크립터와 시그널 핸들러도 공유 자원이다.
공유 구조는 메모리를 절약하고 데이터 공유를 빠르게 하며 통신 오버헤드를 없앤다. 동시에 접근하는 경우에는 Race Condition을 피하기 위한 동기화가 필요하다.
반대로 Register와 Stack은 쓰레드마다 독립적이다. Register에는 CPU 레지스터, 프로그램 카운터(PC), 스택 포인터(SP)가 포함된다. Stack에는 지역 변수, 함수 매개변수, Return Address가 저장된다. Thread Local Storage(TLS)는 전역 변수처럼 사용할 수 있지만 쓰레드별로 분리된 저장 공간을 제공한다.
공유 자원을 다루는 동기화
동기화의 목적은 공유 자원을 보호하고 데이터 일관성을 유지하며 Race Condition을 막는 데 있다. 대표적인 기법으로는 상호 배제를 위한 Mutex(Mutual Exclusion), Semaphore, Condition Variable, Read-Write Lock이 있다.
잠금을 사용할 때는 여러 잠금이 순환 대기하는 데드락을 경계해야 한다. 자원 공유가 곧바로 안전한 동시 실행을 뜻하지는 않는다.
병렬 작업을 설계하는 방식
멀티쓰레드 프로그램은 동시에 여러 작업을 수행해 응답성을 높이고 백그라운드 작업을 처리할 수 있다. 웹 브라우저의 UI 쓰레드와 다운로드 쓰레드, 웹 서버의 클라이언트별 쓰레드 할당, 게임의 렌더링 쓰레드와 물리 엔진 쓰레드가 그 예다.
병렬화하기 적합한 작업에는 독립적인 계산, 데이터 병렬 처리, 파이프라인 처리가 있다. 이론적으로 N개 코어는 N배 성능을 기대할 수 있지만, 실제 성능은 오버헤드와 동기화를 고려하면 0.7N ~ 0.9N배다.
프로세스는 자원 할당 단위이고 쓰레드는 실행 단위라는 역할 분리는 분할과 정복 방식의 설계로 이어진다. 각 쓰레드에 한 가지 작업을 맡기고 독립적인 실행 흐름을 구성하면 모듈화를 유지하기 쉽다.
커널 쓰레드에서는 OS가 쓰레드별로 CPU 시간을 배분하고 우선순위를 설정할 수 있다. 공정한 CPU 시간 배분, 우선순위 기반 스케줄링, 실시간 스케줄링도 지원한다.
Ready에서 종료까지의 상태
쓰레드는 Ready, Run, Sleep(Blocked), Terminated 상태를 거친다. Ready는 실행 대기 상태이고 Run은 실행 중인 상태다. I/O 대기 시에는 Sleep(Blocked)로 이동하며, I/O가 끝나면 다시 Ready 상태로 돌아간다. 실행이 끝나면 Terminated 상태가 된다.
우선순위가 높은 쓰레드가 먼저 실행될 수 있으며, 동적 우선순위 조정과 실시간 우선순위도 지원된다.
프로세스와 쓰레드의 경계
| 구분 | 프로세스 | 쓰레드 |
|---|---|---|
| 정의 | 실행 중인 프로그램 | 프로세스 내 실행 단위 |
| 메모리 | 독립적 | 공유 (Code, Data, Heap) |
| 자원 할당 | 자원 할당 단위 | 최소한의 자원만 소유 |
| Context Switch | 오버헤드 큼 (수십 μs) | 오버헤드 작음 (수 μs) |
| 통신 | IPC 필요 | 직접 메모리 접근 |
| 생성 비용 | 높음 | 낮음 (10-100배 빠름) |
| 안정성 | 높음 (격리) | 낮음 (공유로 인한 충돌) |
| 독립성 | 완전 독립 | 부분 독립 (Stack, Register) |
| 실행 순서 | 예측 가능 | 비결정적 |