프로세스와 스레드의 차이

study·11 min read·2026-08-21(Updated 2026-08-21)
목차

한 줄 요약

프로세스는 독립된 메모리 공간을 가진 실행 단위이고, 스레드는 같은 프로세스 안에서 메모리를 공유하며 실행되는 흐름의 단위다. 이 차이 하나가 자원 사용 비용, 통신 방식, 장애가 퍼지는 범위를 전부 다르게 만든다.

왜 (배경/문제 상황)

“프로세스와 스레드의 차이를 설명해보세요”는 정말 자주 나오는 질문이지만, “프로세스는 메모리를 독립적으로 쓰고 스레드는 공유한다”까지만 외워두면 꼬리 질문(왜 스레드가 더 가벼운지, 통신은 어떻게 하는지, 하나가 죽으면 어떻게 되는지)에서 막히기 쉽다. 표면적인 정의를 넘어 왜 그런 차이가 생기는지까지 정리한다.

본문

1. 정의와 메모리 구조

  • 프로세스(Process): OS가 자원을 할당하는 단위. 실행 중인 프로그램의 인스턴스로, Code/Data/Heap/Stack을 포함한 독립된 주소 공간(Address Space)을 갖는다.
  • 스레드(Thread): 프로세스 내에서 실제로 실행되는 흐름의 단위. 프로세스는 최소 1개 이상의 스레드(메인 스레드)를 갖는다.
영역프로세스 간같은 프로세스의 스레드 간
Code(코드)독립공유
Data(전역변수)독립공유
Heap(동적 할당)독립공유
Stack(지역변수, 함수 호출)독립스레드마다 독립

스레드는 Stack만 자기 것을 따로 갖고, 나머지(Code/Data/Heap)는 같은 프로세스에 속한 다른 스레드와 공유한다. 이게 이후에 나오는 비용·통신·장애 전파 차이 전부의 근본 원인이다.

2. 왜 스레드가 더 가벼운가 (컨텍스트 스위칭)

컨텍스트 스위칭은 CPU가 한 실행 흐름에서 다른 실행 흐름으로 넘어갈 때, 현재 상태(레지스터, 프로그램 카운터 등)를 저장하고 다음 실행 흐름의 상태를 복원하는 과정이다.

  • 프로세스 전환: 서로 다른 주소 공간으로 넘어가야 하므로, CPU의 메모리 관리 유닛(MMU)이 참조하는 페이지 테이블(Page Table)까지 교체해야 한다. 이 과정에서 주소 변환 캐시(TLB)의 캐시 적중률이 떨어져 이후 메모리 접근이 한동안 느려지는 경향이 있다.
  • 스레드 전환: 같은 프로세스 안의 스레드끼리는 주소 공간이 동일하므로 페이지 테이블을 바꿀 필요가 없다. 레지스터와 스택 포인터 정도만 바꾸면 되어 상대적으로 가볍다.

즉 “스레드가 가볍다”는 건 생성 비용뿐 아니라 실행 중 전환 비용도 프로세스보다 작다는 뜻이다.

3. 통신 방식이 다르다 (IPC vs 공유 메모리)

프로세스는 주소 공간이 격리되어 있어서, 한 프로세스가 다른 프로세스의 메모리를 직접 읽거나 쓸 수 없다. 그래서 프로세스 간에 데이터를 주고받으려면 OS가 제공하는 IPC(Inter-Process Communication) 메커니즘이 필요하다 — 파이프, 소켓, 공유 메모리 세그먼트, 메시지 큐 등.

반면 같은 프로세스의 스레드들은 Data/Heap을 공유하므로, 전역 변수나 힙에 할당한 객체를 그냥 참조하는 것만으로 통신이 된다. 대신 이게 새로운 문제를 만든다 — 여러 스레드가 같은 데이터를 동시에 건드리면 레이스 컨디션(race condition)이 생기므로, 뮤텍스(mutex)나 세마포어 같은 동기화 도구로 접근을 통제해야 한다.

4. 장애 전파 범위가 다르다

프로세스는 주소 공간이 격리돼 있어서, 한 프로세스가 크래시해도 원칙적으로 다른 프로세스에는 영향을 주지 않는다. 반면 같은 프로세스 안의 스레드 하나가 처리되지 않은 예외나 세그멘테이션 폴트로 죽으면, Code/Data/Heap을 공유하는 같은 프로세스의 다른 스레드도 함께 죽을 수 있다. 스레드는 “격리”가 아니라 “협력”을 전제로 설계된 실행 단위이기 때문이다.

5. 실무에서는 어떻게 나뉘어 쓰이나

  • Python: 일반적인(GIL이 활성화된) CPython 빌드는 GIL(Global Interpreter Lock)이 한 번에 하나의 스레드만 파이썬 바이트코드를 실행하도록 강제한다. 그래서 CPU 연산이 많은 작업은 멀티스레딩으로 진짜 병렬 처리가 안 되고, 별도 프로세스(multiprocessing)를 띄워야 한다. 반대로 I/O 대기가 많은 작업(네트워크 요청 등)은 대기 중에 GIL이 풀리므로 멀티스레딩만으로도 효과가 있다. (CPython 3.13부터는 GIL을 아예 끈 free-threaded 빌드가 실험적으로 제공되는데, 이 빌드에서는 CPU 연산도 여러 코어에서 병렬로 돈다 — 다만 GIL 비활성화를 지원하지 않는 확장 모듈을 로드하면 GIL이 다시 켜질 수 있다.)
  • Node.js: 메인 실행은 단일 스레드 이벤트 루프이지만, 파일 I/O 등 일부 작업은 내부적으로 워커 스레드 풀(libuv)에 위임한다. “싱글 스레드”라는 말이 “스레드를 아예 안 쓴다”는 뜻은 아니다.
  • Java: 스레드풀 기반의 멀티스레드 웹 서버가 흔하다. 요청마다 스레드를 새로 만드는 대신, 미리 만들어둔 스레드풀에서 꺼내 쓰고 반납해 스레드 생성 비용을 줄인다.

예제

공유 메모리 때문에 생기는 문제를 의사코드로 보면 다음과 같다. 두 스레드가 동시에 같은 카운터를 1씩 증가시키는 상황이다.

공유 변수: counter = 0

스레드 A                       스레드 B
1. counter 값을 읽는다 (0)
                                1. counter 값을 읽는다 (0)
2. 1을 더한다 (0 + 1 = 1)
                                2. 1을 더한다 (0 + 1 = 1)
3. counter에 1을 쓴다
                                3. counter에 1을 쓴다 (덮어씀)

결과: counter = 1 (기대값은 2)

counter++ 하나가 실제로는 “읽기 → 더하기 → 쓰기” 세 단계로 나뉘기 때문에, 두 스레드의 실행이 겹치면 한쪽 결과가 사라진다. 이걸 막으려면 세 단계를 하나의 원자적(atomic) 구간으로 묶어야 하는데, 그 역할을 하는 게 뮤텍스다.

스레드 A                       스레드 B
1. 뮤텍스 잠금 획득
2. counter 값을 읽는다 (0)
3. 1을 더한다 (1)
4. counter에 1을 쓴다
5. 뮤텍스 잠금 해제
                                1. 뮤텍스 잠금 시도 → A가 갖고 있어 대기
                                (A가 해제한 뒤) 잠금 획득
                                2. counter 값을 읽는다 (1)
                                3. 1을 더한다 (2)
                                4. counter에 1을 쓴다
                                5. 뮤텍스 잠금 해제

결과: counter = 2 (기대값과 일치)

뮤텍스는 “읽기-수정-쓰기” 구간을 한 번에 한 스레드만 통과하도록 강제해서, 두 스레드의 실행 순서가 겹쳐도 항상 올바른 결과가 나오게 만든다. 대신 이 구간에서는 다른 스레드가 대기해야 하므로, 잠금 구간이 넓을수록 동시성 이점이 줄어든다는 트레이드오프가 있다.

주의사항

  • “스레드가 프로세스보다 무조건 좋다”는 아니다. 스레드는 격리가 없어서 동기화 버그(레이스 컨디션, 데드락)에 취약하고, 하나가 죽으면 전체가 죽을 위험도 있다. 반대로 프로세스는 격리된 만큼 안전하지만 통신·전환 비용이 크다. 얼마나 격리가 필요한지와 얼마나 자주 통신·전환이 필요한지를 함께 고려해서 선택해야 한다.
  • 여기서 설명한 컨텍스트 스위칭 비용 차이는 일반적인 경향이다. 실제 비용은 OS와 하드웨어(예: TLB에 프로세스 식별자를 함께 저장해 전환 시에도 캐시를 일부 재사용하는 기능)에 따라 달라질 수 있다.
  • 멀티스레드 프로그램의 동시성 버그(레이스 컨디션, 데드락)는 항상 재현되는 게 아니라 타이밍에 따라 간헐적으로 나타나므로, 디버깅이 특히 어렵다는 점도 함께 알아두면 좋다.

참고자료