프로세스와 스레드의 차이: 메모리 공유가 가르는 모든 것

5 분 소요

면접 단골 질문이자, 서버 설정 파일에서 매일 만나는 실전 개념입니다. nginx의 worker_processes, gunicorn의 --workers--threads, 자바의 스레드 풀이 전부 이 개념 위에 있습니다. 프로세스와 스레드의 차이를 아느냐는 결국 이 설정값들이 무엇을 바꾸는지 아느냐는 질문입니다. 차이의 뿌리는 하나입니다. 메모리를 공유하는가. 이 글은 그 한 기준에서 나머지 차이를 전부 끌어냅니다.

정의: 실행의 단위와 그 안의 흐름 #

프로세스는 실행 중인 프로그램 하나이고, OS가 자원을 배분하는 단위입니다. 프로세스마다 자기만의 가상 메모리 공간(코드, 힙, 스택), 파일 디스크립터, 권한을 가집니다. 서로의 메모리는 보이지도 않고 건드릴 수도 없습니다. 이 격리는 커널과 하드웨어(MMU)가 강제합니다.

스레드는 한 프로세스 안의 실행 흐름입니다. 같은 프로세스의 스레드들은 코드, 힙, 파일 디스크립터를 공유하고, 각자의 스택과 레지스터만 따로 가집니다. 커널 스케줄러가 CPU에 올리는 단위는 스레드라서, “실행의 단위는 스레드, 자원의 단위는 프로세스"라는 요약이 성립합니다.

공유가 가르는 세 가지 실무 차이 #

첫째, 통신 비용: 스레드끼리는 같은 힙의 변수를 그냥 읽고 쓰면 됩니다. 빠르지만, 동시에 같은 데이터를 건드리는 순간 경쟁 조건이 생기므로 락이 필요해집니다. 멀티스레드 프로그래밍의 어려움(데드락, 레이스)은 전부 이 공유에서 나옵니다. 프로세스끼리는 메모리가 격리돼 있어 파이프, 소켓, 공유 메모리 구간 같은 IPC를 명시적으로 써야 합니다. 느리고 번거롭지만, 실수로 서로의 데이터를 밟을 방법 자체가 없습니다.

둘째, 생성과 전환 비용: 프로세스 생성은 메모리 공간을 새로 마련하는 일이라 비싸고(리눅스가 이를 싸게 만드는 장치가 다음 편의 copy-on-write입니다), 스레드 생성은 스택 하나 얹는 일이라 쌉니다. 컨텍스트 스위치도 갈립니다. 같은 프로세스의 스레드 간 전환은 메모리 공간이 그대로라 상대적으로 싸고, 프로세스 간 전환은 주소 공간이 바뀌면서 캐시·TLB가 식는 비용이 얹힙니다. 서버가 느린 이유 #1에서 본 과도한 컨텍스트 스위치 이야기가 바로 이 비용입니다.

셋째, 장애의 폭발 반경: 스레드 하나가 메모리를 잘못 건드려 죽으면 프로세스 전체가 죽습니다. 힙을 공유하므로 한 스레드의 오염이 모두의 오염이기 때문입니다. 프로세스는 하나가 죽어도 이웃이 멀쩡합니다. 이 성질이 아래 설계 선택들의 핵심 근거가 됩니다.

실제 소프트웨어는 무엇을 골랐나 #

교과서 비교보다 실물이 무엇을 선택했는지 보면 감각이 빨리 잡힙니다.

  • nginx: 워커 프로세스를 여러 개 둡니다. 워커 하나가 죽어도 서비스가 살고, 워커끼리 공유할 상태가 거의 없는 구조라 격리의 이점만 취합니다.
  • 웹 브라우저: 탭마다 프로세스를 씁니다. 탭 하나가 죽어도 브라우저가 살아남게, 그리고 사이트 간 메모리 격리(보안)를 위해 비싼 쪽을 감수한 선택입니다.
  • 자바·Go 서버: 프로세스 하나에 스레드(고루틴) 다수를 둡니다. 요청들이 커넥션 풀·캐시 같은 상태를 공유해야 하므로 공유 메모리의 이점이 크고, 언어·런타임이 동시성 도구를 잘 갖춘 경우입니다.
  • 파이썬 웹 서버(gunicorn 등): 프로세스 × 스레드 혼합입니다. CPython의 GIL 때문에 한 프로세스의 스레드들은 CPU 작업을 병렬로 못 하므로, CPU 활용은 프로세스 수로 얻고 I/O 대기 동시성은 스레드로 얻는 절충입니다. GIL의 자세한 이야기는 모던 파이썬 고급 #5에서 다뤘습니다.

이 목록이 워커 설정의 독해법이 됩니다. “프로세스 수는 격리와 CPU 병렬성, 스레드 수는 공유 상태와 I/O 동시성"이라는 축으로 읽으면, 프레임워크 문서의 권장값(예: 프로세스는 코어 수 기준, 스레드는 I/O 비중 기준)이 왜 그런지 보입니다.

헷갈리기 쉬운 두 가지 #

  • “스레드가 항상 더 빠르다"는 오해: 생성·전환이 싼 것이지 실행이 빠른 것이 아닙니다. CPU를 쓰는 계산의 속도는 같고, 오히려 공유 데이터의 락 경합이 심하면 멀티스레드가 멀티프로세스보다 느려집니다.
  • 동시성 ≠ 스레드: 스레드는 동시성을 얻는 여러 수단 중 하나입니다. 비동기 I/O(이벤트 루프)는 스레드 하나로 수만 커넥션을 다루고, 파이썬 asyncio·Node.js가 이 모델입니다. “동시 접속이 많다 = 스레드를 늘린다"가 아니라, 작업이 CPU 중심인지 I/O 대기 중심인지가 먼저입니다.

정리 #

  • 차이의 뿌리는 메모리 공유입니다. 프로세스는 격리된 자원의 단위, 스레드는 그 안에서 메모리를 공유하는 실행 흐름입니다.
  • 공유는 통신을 싸게 하는 대신 락과 레이스를 부르고, 격리는 통신을 비싸게 하는 대신 장애의 폭발 반경을 좁힙니다.
  • 생성·전환 비용은 스레드가 싸지만, 실행 속도가 빨라지는 것은 아닙니다.
  • 실물의 선택(nginx의 프로세스, 자바의 스레드, 파이썬의 혼합)은 전부 “공유할 상태가 있는가, 죽었을 때 무엇까지 죽어도 되는가"의 답입니다.
  • 워커 설정은 프로세스 수 = 격리·CPU 병렬성, 스레드 수 = I/O 동시성이라는 축으로 읽습니다.

다음 편은 프로세스를 만드는 순간의 이야기입니다. fork가 어떻게 무거운 프로세스 생성을 싸게 만드는지, copy-on-write를 다룹니다.

X