에듀테크 프로젝트 대시보드
프로세스 vs 스레드
프로세스는 실행 중인 프로그램 하나로, 자기만의 독립된 메모리 공간을 가진다. 스레드는 그 프로세스 안에서 나뉘어 일하는 실행 흐름으로, 코드·데이터·힙을 공유하고 스택만 따로 가진다. 공유하기 때문에 빠르지만, 같은 값을 동시에 건드리면 문제가 생긴다. 아래에서 직접 확인해 보자.
↑ 스레드 1·2는 Code/Data/Heap 공유, Stack만 각자
A와 B가 데이터를 주고받으려면 IPC(파이프·소켓·공유메모리)가 필요
counter += 1 은 사실 ①읽기 → ②더하기 → ③쓰기 3단계다. 두 스레드가 ①을 같은 값으로 읽으면, 한 번의 증가가 사라진다(lost update). 실행 버튼을 눌러 보고, Lock을 켜서 다시 실행해 비교해 보자.
| 구분 | 프로세스 (Process) | 스레드 (Thread) |
|---|---|---|
| 메모리 | 독립된 주소 공간 | Code·Data·Heap 공유, Stack만 별도 |
| 생성 비용 | 무겁다 (주소 공간·자원 복제) | 가볍다 (스택·레지스터만) |
| context switch | 느림 (주소 공간·TLB 교체) | 빠름 (같은 공간 내 전환) |
| 통신 | IPC 필요 (파이프·소켓·공유메모리) | 전역·힙 변수 직접 접근 |
| 안정성 | 하나가 죽어도 다른 프로세스는 무사 | 한 스레드의 크래시가 프로세스 전체를 종료 |
| 동기화 | 덜 필요 (격리되어 있음) | Mutex·Semaphore 등 필수 |
| 예시 | Chrome 탭마다 별도 프로세스 | 한 탭 안의 렌더링·네트워크·JS 스레드 |
환경마다 다르지만, 스레드 생성은 프로세스 생성(fork+exec)보다 통상 10~100배 저렴하다.
counter += 1 한 줄이 위험한가?CPU 입장에서 이 한 줄은 메모리에서 값 로드 → 레지스터에서 +1 → 메모리에 저장의 여러 명령이다.
스레드 A가 값을 로드한 직후 OS가 스레드 B로 전환하면, B도 같은(옛날) 값을 로드해 +1 한다.
결국 두 번 증가했지만 결과는 +1 — 이것이 lost update다.
Lock·Mutex는 이 3단계를 다른 스레드가 끼어들 수 없는 하나의 덩어리(임계 구역)로 묶어준다.
GIL은 "한 번에 한 스레드만 파이썬 바이트코드를 실행"하게 하지만,
counter += 1은 여러 바이트코드로 나뉘고 그 사이에서 스레드 전환이 일어난다.
그래서 GIL이 있어도 락 없이 공유 변수를 수정하면 값이 틀어질 수 있다.
또 CPU 바운드 작업은 GIL 때문에 스레드로 병렬 가속이 안 되므로, 이럴 땐 multiprocessing(프로세스)을 쓴다.
스레드: 작업들이 같은 데이터를 자주 공유하고, I/O 대기(네트워크·디스크)가 많을 때. 가볍고 통신이 쉽다.
프로세스: 안정성·보안 격리가 중요하거나(브라우저 탭), CPU를 꽉 쓰는 계산을 여러 코어로 진짜 병렬 처리할 때.
하나가 죽어도 나머지가 살아남는다.
CPU가 실행 중인 흐름을 바꿀 때, 현재 레지스터·프로그램 카운터를 저장하고 다음 것을 불러온다.
프로세스 전환은 여기에 더해 가상메모리 매핑(페이지 테이블)과 TLB 캐시까지 갈아끼워야 해서
캐시 미스가 쏟아진다. 스레드 전환은 같은 주소 공간을 쓰므로 이 과정을 건너뛰어 훨씬 빠르다.