성능 이슈나 동시성 문제를 다룰 때 반드시 이해하고 있어야 하는 개념인 프로세스와 스레드에 대해서 단순 정의를 넘어서 메모리 구조, 생성 비용, 컨텍스트 스위칭, 그리고 실무 관점 차이까지 정리해보겠습니다.
실행중인 프로그램의 인스턴스로, 운영체제로부터 독립적인 메모리 공간을 할당받아 실행되는 단위입니다.
예를 들어 크롬 브라우저 / IntelliJ 실행 -> 하나 이상의 프로세스 생성
프로세스는 각각 독립적인 메모리 공간을 가집니다.
[Code 영역] -> 실행할 코드
[Data 영역] -> 전역 변수
[Heap 영역] -> 동적 할당 메모리
[Stack 영역] -> 함수 호출 정보
여기서 중요한 포인트는 프로세스 간에는 메모리 공유를 하지 않습니다.
그래서 다른 프로세스와 통신하려면 IPC, Socket, Pipe, Message Queue 같은
별도 통신 방식이 필요합니다.
프로세스의 특징을 정리해보면, 독립적으로 실행되고, 메모리를 보호하여 안정성이 높으며, 생성 비용과 컨텍스트 스위치 비용이 높다는 점입니다.
프로세스 내부에서 실행되는 작업 단위로,
하나의 프로세스 안에서 여러 개의 스레드가 실행될 수 있습니다.
스레드는 프로세스의 자원을 공유합니다.
// 공유
[Code 영역] -> 실행할 코드
[Data 영역] -> 전역 변수
[Heap 영역] -> 동적 할당 메모리
// 개별 (스레드별로 독립)
[Stack 영역] -> 함수 호출 정보
스레드의 특징을 정리해보면, 생성 비용과 컨텍스트 스위칭 비용이 적고, 자원 공유가 쉬우며 대신 동기화 문제가 발생할 수 있다는 점입니다.
운영체제가 CPU를 다른 실행 단위로 넘길 때 발생하는 작업입니다.
프로세스 컨텍스트 스위칭은 레지스터 저장, PCB 변경, 메모리 맵 교체, TLB 플러시 등
많은 작업을 수반하기 때문에 비용이 높습니다.
스레드 컨텍스트 스위칭은 레지스터 저장, 스택 변경 정도로 간단하기 때문에 프로세스 대비 가볍게 (낮은 비용으로) 일어납니다.
프로세스와 스레드를 둘러싼 기타 개념들을 알아보겠습니다.
스레드는 자원을 공유하기 때문에 Race Condition (두 스레드가 동시에 같은 자원을 수정),
Deadlock (서로가 서로의 자원을 기다리는 상태) 등의 문제가 발생합니다.
그래서 Mutex, Semaphore, Monitor 같은 동기화 도구가 필요합니다.
함수 호출 정보, 지역 변수 충돌 방지를 위해서 스레드마다 Stack 영역이 독립적입니다.
요청 하나당 프로세스를 생성한다면, 메모리가 낭비되고 생성 비용이 높아지게 됩니다.
그래서 하나의 프로세스, 여러 개의 스레드로 처리하면 효율적입니다.
멀티프로세스는 안정성이 높지만 그만큼 비용이 높고,
멀티스레드는 성능이 높지만, 동기화 문제가 발생할 수 있습니다.
JavaScript 실행은 싱글 스레드, 내부적으로는 멀티스레드 (libuv, worker)
안정성이 중요한 경우, 독립적인 실행이 필요한 경우, 하나가 죽어도 다른 작업을 유지해야 하는 경우는
프로세스가 적합합니다. (예시: 브라우저 탭)
자원 공유가 필요한 경우, 빠른 처리와 성능이 중요한 경우, I/O 작업이 많은 서버는 스레드가 적합합니다. (예시: 웹서버, 게임 서버)
프로세스는 독립적인 샐행 단위, 스레드는 프로세스 내부의 작업 단위로,
안정성을 원하면 프로세스 / 성능과 효율을 원하면 스레드 사용하는 것이 일반적입니다.
하지만 스레드를 사용한다면 반드시 동기화와 공유 자원 문제를 이해해야 합니다.