쓰레드의 메모리 공유와 동기화 원리

프로세스 안에서 동작하는 쓰레드의 메모리 구조, 전환 비용, 실행 모델과 동기화 기법을 실무 관점에서 정리합니다.

2026-08-14 · 최초 발행 2026-01-03

프로세스 안에서 실행 흐름을 나누는 방식

쓰레드는 CPU가 실행하는 기본 작업 단위다. 하나의 프로세스는 여러 쓰레드를 포함할 수 있으며, 이들은 Code, Data, Heap 영역을 함께 사용한다. 반면 실행 문맥에 해당하는 Register와 Stack은 쓰레드마다 따로 유지한다.

프로세스가 자원을 할당받는 단위라면, 쓰레드는 그 자원을 바탕으로 실제 실행을 이어 가는 단위다. 이 구조 때문에 쓰레드는 Light Weight Process라고도 부른다.

프로세스
├── Code (공유)
├── Data (공유)
├── Heap (공유)
├── Thread 1 (Stack, Register)
├── Thread 2 (Stack, Register)
└── Thread 3 (Stack, Register)
ProcessCode(공유)Data(공유)Heap(공유)Thread 1Thread 2Thread 3Stack 1(독립)Register 1(독립)Stack 2(독립)Register 2(독립)Stack 3(독립)Register 3(독립)

가벼운 실행 단위가 되는 이유

쓰레드는 프로세스보다 생성과 삭제 비용이 낮고, 생성 속도는 프로세스의 10~100배 빠르다. Code, Data, Heap뿐 아니라 파일 디스크립터와 시그널 핸들러도 공유한다.

그 대신 각 쓰레드는 자신만의 Stack, Register Set, Program Counter (PC)를 관리한다. 같은 프로세스의 자원을 공유하면서도 서로 다른 실행 위치와 호출 상태를 유지할 수 있는 이유다.

프로세스 간 Context Switch에서는 전체 메모리 맵 교체, TLB 플러시, 캐시 무효화가 발생하며 비용은 수십 마이크로초다. 쓰레드 간 전환은 Register와 Stack을 교체하는 동안 메모리 맵을 유지하고 캐시 일부도 유지할 수 있어 비용이 수 마이크로초다. Context Switch 시간은 10~100배 감소하며, 작업 전환이 잦은 환경의 응답성에도 영향을 준다.

Thread A실행Context SwitchRegister 저장Register 복원Thread B실행메모리유지캐시일부 유지

멀티코어 환경에서는 각 쓰레드를 서로 다른 코어에서 실행할 수 있다. 이론적으로 N개 코어는 N배 성능으로 이어질 수 있지만, 오버헤드를 고려한 실제 성능은 0.7N ~ 0.9N배다. 독립적인 계산, 데이터 병렬 처리, 파이프라인 처리가 병렬화 대상이 된다.

I/O를 기다리는 동안 다른 쓰레드가 CPU를 쓰게 하는 것도 중요한 활용 방식이다.

단일 쓰레드: [작업] [I/O 대기] [작업] [I/O 대기]
멀티 쓰레드: [작업1] + [작업2] (I/O 대기 중 다른 작업 수행)

한 쓰레드가 I/O 대기 상태여도 다른 쓰레드가 실행될 수 있으므로 CPU 유휴 시간을 줄이고 전체 처리량을 높일 수 있다.

관리 주체에 따른 실행 모델

커널 쓰레드는 커널이 존재를 인지하고 OS가 직접 관리·스케줄링하는 방식이다. 시스템 콜로 생성하고 관리하며, 멀티코어에서 병렬 실행할 수 있다. 한 쓰레드가 블록되어도 다른 쓰레드는 계속 실행할 수 있고 안정성이 높다. 다만 생성과 관리 비용, 시스템 콜 오버헤드, 쓰레드 수 제한을 고려해야 한다. POSIX 쓰레드(pthread), Windows 쓰레드, Linux NPTL (Native POSIX Thread Library)이 예시다.

사용자 쓰레드는 커널이 쓰레드의 존재를 인식하지 못하고, 사용자 공간의 라이브러리가 관리한다. 시스템 콜 없이 빠르게 생성하고 전환할 수 있으며 쓰레드 수 제한이 없고 이식성이 높다. 반면 멀티코어 활용에는 제한이 있고, 한 쓰레드가 블록되면 프로세스 전체가 블록될 수 있으며 스케줄링 공정성도 낮다. Green Thread (Java 초기 버전), GNU Portable Threads, Fiber (Windows)가 여기에 해당한다.

하이브리드 모델인 M:N 모델은 M개 사용자 쓰레드를 N개 커널 쓰레드에 연결한다. 사용자 쓰레드의 유연성과 커널 쓰레드의 멀티코어 활용을 결합해 최적의 성능을 목표로 한다. Go 언어의 Goroutine, Erlang 프로세스, Solaris 쓰레드 (과거)가 예시다.

쓰레드종류Kernel ThreadUser ThreadHybrid(M:N)OS 직접 관리멀티코어 활용라이브러리 관리빠른 생성양쪽 장점결합

함께 쓰는 메모리와 분리된 실행 문맥

Code (Text) 영역에는 실행 코드인 기계어가 있으며 읽기 전용으로 모든 쓰레드가 같은 코드를 실행한다. Data 영역의 전역 변수와 Static 변수도 쓰레드 간에 공유된다. Heap의 동적 할당 메모리(malloc, new) 역시 공유할 수 있으므로 동기화가 필요하다.

Register에는 CPU 레지스터, Program Counter (PC), Stack Pointer (SP)가 포함되며 각 쓰레드에 독립적이다. Stack 역시 지역 변수, 함수 매개변수, Return Address를 보관하는 개별 공간이다. Thread Local Storage (TLS)는 전역 변수처럼 사용하지만 쓰레드별로 분리된 저장 공간을 제공한다.

ProcessMemory공유 영역독립 영역CodeDataHeapThread 1Stack + RegisterThread 2Stack + RegisterThread 3Stack + Register

실행 상태가 바뀌는 경로

쓰레드는 생성 중인 New, CPU 할당을 기다리는 Ready, 실행 중인 Run, I/O 또는 이벤트를 기다리는 Blocked (Sleep), 종료된 Terminated의 5가지 상태를 거친다.

DispatchTimeoutI/O 요청I/O 완료NewReadyRunBlockedTerminated

공유 자원이 만든 동기화 문제

여러 쓰레드가 공유 자원에 동시에 접근하면 실행 순서에 따라 결과가 달라지는 Race Condition이 생길 수 있다. 이로 인해 데이터 일관성이 손상된다.

공유 자원에 접근하는 Critical Section은 한 번에 하나의 쓰레드만 실행하도록 상호 배제(Mutual Exclusion)를 보장해야 한다. 목적과 접근 형태에 따라 다음 기법을 사용한다.

  • Mutex (Mutual Exclusion): 한 번에 하나의 쓰레드만 접근하도록 Lock/Unlock을 사용한다.
  • Semaphore: 카운팅 세마포어로 N개의 쓰레드 동시 접근을 허용하며 P(wait) / V(signal)을 사용한다.
  • Condition Variable: 조건이 만족될 때까지 wait하고 signal 또는 broadcast로 깨운다.
  • Read-Write Lock: 읽기는 여러 쓰레드가 동시에 수행하고, 쓰기는 하나의 쓰레드만 수행한다.

동기화 설계에서는 Deadlock, Livelock, Starvation도 함께 방지해야 한다.

공유 자원접근동기화필요MutexSemaphoreConditionVariableRW Lock데이터일관성 보장

프로세스와 쓰레드의 운영상 차이

구분 프로세스 쓰레드
정의 실행 중인 프로그램 프로세스 내 실행 단위
메모리 독립적 공유 (Code, Data, Heap)
자원 할당 자원 할당 단위 최소한의 자원
Context Switch 오버헤드 큼 (수십 μs) 오버헤드 작음 (수 μs)
통신 IPC 필요 직접 메모리 접근 가능
생성 비용 높음 낮음 (10~100배 빠름)
안정성 높음 (격리) 낮음 (공유로 인한 충돌)
독립성 완전 독립 부분 독립

멀티 쓰레드 프로그램에서 역할을 나누는 법

웹 브라우저는 UI 쓰레드, 다운로드 쓰레드, 렌더링 쓰레드로 역할을 나눌 수 있다. 웹 서버는 클라이언트마다 쓰레드를 할당해 동시 다중 요청을 처리하고, 쓰레드 풀(Thread Pool)을 사용할 수 있다. 게임 엔진에서는 렌더링, 물리 엔진, AI, 네트워크 작업을 각각의 쓰레드에 둘 수 있다.

역할은 한 쓰레드가 한 가지 작업을 맡도록 나누고, 독립적인 실행 흐름과 모듈화를 유지하는 편이 낫다. Lock 범위를 최소화하고 Lock-Free 자료구조나 원자적 연산(Atomic Operation)을 활용해 동기화 의존도를 낮춘다. 여러 Lock을 사용할 때는 Lock 순서를 일관되게 유지하고, Timeout 설정과 데드락 탐지·복구도 고려한다.

쓰레드운영체제동기화프로세스멀티코어