한양과학기술고등학교

에듀테크 프로젝트 대시보드

프로세스 vs 스레드

메모리는 어떻게 나뉘고, 경쟁 상태는 왜 생길까?

프로세스는 실행 중인 프로그램 하나로, 자기만의 독립된 메모리 공간을 가진다. 스레드는 그 프로세스 안에서 나뉘어 일하는 실행 흐름으로, 코드·데이터·힙을 공유하고 스택만 따로 가진다. 공유하기 때문에 빠르지만, 같은 값을 동시에 건드리면 문제가 생긴다. 아래에서 직접 확인해 보자.

1 메모리 구조 — 무엇을 나누고 무엇을 공유하나

프로세스 A

Code (코드)기계어 명령. 읽기 전용
Data (전역/정적 변수)프로그램 전체가 공유
Heap (동적 할당)malloc / new — 실행 중 커짐 ↓
Stack
스레드 1지역변수·호출
Stack
스레드 2스레드마다 별도

↑ 스레드 1·2는 Code/Data/Heap 공유, Stack만 각자

프로세스 B 완전 분리

CodeA의 메모리에 접근 불가
DataOS가 주소 공간을 격리
Heap서로 주소가 겹쳐 보여도 물리적으론 다름
Stack
스레드 1

A와 B가 데이터를 주고받으려면 IPC(파이프·소켓·공유메모리)가 필요

2 경쟁 상태(Race Condition) 시뮬레이터
각 스레드가 공유 변수를 5번씩 +1 → 정답은 10
🧵 스레드 A
공유 변수 counter
0
목표: 10
🧵 스레드 B

counter += 1 은 사실 ①읽기 → ②더하기 → ③쓰기 3단계다. 두 스레드가 ①을 같은 값으로 읽으면, 한 번의 증가가 사라진다(lost update). 실행 버튼을 눌러 보고, Lock을 켜서 다시 실행해 비교해 보자.

3 한눈에 비교
구분프로세스 (Process)스레드 (Thread)
메모리독립된 주소 공간Code·Data·Heap 공유, Stack만 별도
생성 비용무겁다 (주소 공간·자원 복제)가볍다 (스택·레지스터만)
context switch느림 (주소 공간·TLB 교체)빠름 (같은 공간 내 전환)
통신IPC 필요 (파이프·소켓·공유메모리)전역·힙 변수 직접 접근
안정성하나가 죽어도 다른 프로세스는 무사한 스레드의 크래시가 프로세스 전체를 종료
동기화덜 필요 (격리되어 있음)Mutex·Semaphore 등 필수
예시Chrome 탭마다 별도 프로세스한 탭 안의 렌더링·네트워크·JS 스레드

생성 / 전환 비용 (대략적인 상대 비교)

환경마다 다르지만, 스레드 생성은 프로세스 생성(fork+exec)보다 통상 10~100배 저렴하다.

# 파이썬 threading — 공유 변수와 Lock import threading counter = 0 lock = threading.Lock() def worker(): global counter for _ in range(100000): with lock: # 이 블록은 한 번에 한 스레드만 counter += 1 # 읽기-더하기-쓰기를 원자적으로 보호 ts = [threading.Thread(target=worker) for _ in range(4)] for t in ts: t.start() for t in ts: t.join() print(counter) # lock 없으면 400000보다 작게 나올 수 있다
💡 왜 counter += 1 한 줄이 위험한가?

CPU 입장에서 이 한 줄은 메모리에서 값 로드 → 레지스터에서 +1 → 메모리에 저장의 여러 명령이다. 스레드 A가 값을 로드한 직후 OS가 스레드 B로 전환하면, B도 같은(옛날) 값을 로드해 +1 한다. 결국 두 번 증가했지만 결과는 +1 — 이것이 lost update다. Lock·Mutex는 이 3단계를 다른 스레드가 끼어들 수 없는 하나의 덩어리(임계 구역)로 묶어준다.

💡 파이썬 GIL이 있는데 왜 경쟁 상태가 생기나?

GIL은 "한 번에 한 스레드만 파이썬 바이트코드를 실행"하게 하지만, counter += 1은 여러 바이트코드로 나뉘고 그 사이에서 스레드 전환이 일어난다. 그래서 GIL이 있어도 락 없이 공유 변수를 수정하면 값이 틀어질 수 있다. 또 CPU 바운드 작업은 GIL 때문에 스레드로 병렬 가속이 안 되므로, 이럴 땐 multiprocessing(프로세스)을 쓴다.

💡 그럼 언제 프로세스, 언제 스레드?

스레드: 작업들이 같은 데이터를 자주 공유하고, I/O 대기(네트워크·디스크)가 많을 때. 가볍고 통신이 쉽다.
프로세스: 안정성·보안 격리가 중요하거나(브라우저 탭), CPU를 꽉 쓰는 계산을 여러 코어로 진짜 병렬 처리할 때. 하나가 죽어도 나머지가 살아남는다.

💡 context switch가 비싸다는 게 무슨 뜻?

CPU가 실행 중인 흐름을 바꿀 때, 현재 레지스터·프로그램 카운터를 저장하고 다음 것을 불러온다. 프로세스 전환은 여기에 더해 가상메모리 매핑(페이지 테이블)과 TLB 캐시까지 갈아끼워야 해서 캐시 미스가 쏟아진다. 스레드 전환은 같은 주소 공간을 쓰므로 이 과정을 건너뛰어 훨씬 빠르다.