프로세스 (Process)
정의
- 실행 중인 프로그램
- 하나의 프로세스는 실행을 위해 독립된 메모리 공간을 할당받는다.
- 각 프로세스는 운영체제에 의해 독립적으로 관리되며, 하나의 프로그램이 여러 프로세스를 생성할 수 있다.
프로세스의 구조
프로세스는 운영체제에서 실행되는 동안, 특정한 메모리 구조를 가진다. 프로세스의 메모리 공간은 다음과 같이 나눌 수 있다.
1. 텍스트 세그먼트 (Text Segment, 코드 세그먼트)
- 역할: 프로그램의 실행 코드를 저장한다. CPU가 실행할 명령어들이 위치하는 곳
- 특징
- 읽기 전용: 작성된 코드가 기계어로 변환되어 텍스트 세그먼트에 저장된다.
- CPU는 텍스트 세그먼트에서 저장된 명령을 가져와 실행한다.
- 예시: 프로그램이 실행되면
add 함수의 실제 코드는 텍스트 세그먼트에 저장된다. 함수 자체의 명령어들이 텍스트 세그먼트에 저장되고 CPU는 이 코드를 하나씩 실행한다.
function add(a, b) {
return a + b;
}
2. 데이터 세그먼트 (Data Segment)
크게 두 가지로 나누어진다. 초기화된 데이터 / 초기화되지 않은 데이터
a) 초기화된 데이터 세그먼트 (Initialized Data Segment)
- 역할: 초기화된 전역 변수나 정적 변수를 저장한다.
- JavaScript의 전역 변수, 모듈 내의 상수나 전역 스코프에서 정의된 값, 다른 언어의
static 변수
- 예시:
globalValue는 프로그램이 시작되기 전에 초기화된 데이터이다. globalValue는 전역에 존재하고, 텍스트 세그먼트에 저장된 코드와 함께 프로그램이 실행될 때 초기화된다.
const glovalValue = 42;
function showValue() {
console.log(globalValue);
}
showValue();
b) 초기화되지 않은 데이터 세그먼트 (BSS Segment, Block Started By Symbol Segment)
- 역할: 초기화되지 않은 전역 변수 또는 정적 변수를 저장한다.
- 개발자가 선언만 하고 초기화하지 않은 전역변수 등이 포함된다.
3. 스택 세그먼트 (Stack Segment)
- 역할: 함수 호출 시 생성되는 지역 변수와 매개변수를 저장한다. 함수가 호출될 때마다 스택 프레임이 만들어지고 함수가 반환되면 스택에서 해당 프레임이 제거된다.
- 특징: 스택은 함수 호출이 끝나면 자동으로 메모리가 해제된다.
- 스택 오버플로우: 함수가 너무 많이 중첩 호출되었을 때 발생할 수 있다. 예를 들어, 재귀 호출이 종료 조건 없이 이루어지면 스택 오버플로우가 발생할 수 있다.
function recursive() {
return recursive();
}
recursive();
4. 힙 세그먼트 (Heap Segment)
- 역할: 동적 메모리 할당이 이루어지는 공간이다. JavaScript에서는 주로 객체(Object), 배열(Array), 함수(Closure) 같은 참조 타입들이 힙에 저장된다.
- C/C++의
malloc, calloc, new 같은 함수나 연산자를 통해 할당한 메모리 공간이 힙에 저장된다.
- 특징: 힙은 스택과 달리 수명이 관리되지 않기 때문에 메모리 누수가 발생할 수 있다. Garbage Collector가 메모리를 정리해주지만, C/C++의 경우 GC가 없기 때문에 개발자가 직접 관리해야 한다.
- 예시: 힙에 데이터(정수 값)가 저장되고, delete 함수로 직접 메모리를 해제한다.
#include <iostream>
int main() {
int* p = new int;
*p = 1220;
delete p;
p = nullptr;
}
프로세스의 특징
- 독립된 실행 단위: 각 프로세스는 독립적인 주소 공간을 갖는다.
- 프로세스 간 통신(IPC): 프로세스 간 데이터를 주고받기 위해서는 운영체제의 도움을 받아야 하며, 이를 프로세스 간 통신(Inter-process Communication, IPC)이라고 한다. IPC의 방식에는 파이프, 메시지 큐, 공유 메모리 등이 있다.
- 컨텍스트 스위칭: 운영체제는 멀티태스킹을 지원하기 위해 여러 프로세스를 빠르게 전환하는데, 이를 컨텍스트 스위칭이라고 한다. 각 프로세스의 상태를 저장하고 복원하는 과정에서 오버헤드가 발생할 수 있다.
스레드 (Thread)
정의
- 프로세스 내에서 실행의 흐름을 담당하는 단위
- 하나의 프로세스에는 하나 이상의 스레드가 존재할 수 있으으며, 이들은 같은 메모리 공간을 공유한다.
- 프로세스의 경량화된 실행 단위로 볼 수 있음
스레드의 구조
스레드는 같은 프로세스의 메모리 공간을 공유하지만, 각각 독립적인 스택을 가진다.
스레드 간에는 다음 메모리 공간을 공유한다:
1. 텍스트 세그먼트: 스레드는 프로세스의 코드를 공유한다.
2. 데이터 세그먼트: 전역 변수와 정적 변수를 공유한다.
3. 힙: 동적으로 할당된 메모리를 공유한다
스레드의 특징
- 가벼운 실행 단위: 스레드는 같은 프로세스 내에서 자원을 공유하기 때문에 스레드 간 전환은 프로세스 간 전환보다 오버헤드가 적다. 즉, 컨텍스트 스위칭이 더 빠르게 이루어진다.
- 동기화 문제: 스레드들은 같은 메모리 공간을 공유하기 때문에 동시성 문제가 발생할 수 있다.
두 스레드가 동시에 같은 메모리 위치에 접근하면 경쟁 상태(Race Condition)가 발생할 수 있다. 이를 방지하기 위해 락(lock)이나 세마포어(semaphore)같은 동기화 기법이 필요하다.
- 병렬 처리: 현대의 CPU는 멀티코어로 구성되어 있어 여러 스레드를 병렬로 실행할 수 있다.
스레드의 예시
JavaScript는 싱글 스레드 언어로 알려져 있지만, 사실 JavaScript 엔진은 비동기 작업을 처리할 때 이벤트 루프를 사용하여 논리적인 동시성을 구현한다.
하지만 Node.js에서는 worker_threads 모듈을 사용하여 실제로 여러 스레드를 생성할 수 있다.
프로세스와 스레드의 차이점
| 특징 | 프로세스 | 스레드 |
|---|
| 정의 | 실행 중인 프로그램의 인스턴스 | 프로세스 내에서 실행의 흐름을 담당하는 단위 |
| 메모리 공간 | 독립적인 메모리 공간을 가짐 | 같은 프로세스 내에서 메모리를 공유함 |
| 통신 방식 | 프로세스 간 통신은 IPC를 사용 | 스레드는 메모리를 공유하므로 통신이 더 빠름 |
| 컨텍스트 스위칭 비용 | 컨텍스트 스위칭 비용이 큼 | 컨텍스트 스위칭 비용이 적음 |
| 오버헤드 | 각 프로세스는 독립적이므로 오버헤드가 큼 | 스레드는 경량화되어 오버헤드가 적음 |
| 동시성 문제 | 독립적이므로 동시성 문제 발생이 적음 | 메모리를 공유하므로 동기화 기법이 필요함 |
| 실행 단위 | 프로세스는 독립적인 실행 단위임 | 스레드는 프로세스의 경량화된 실행 단위임 |
현대 시스템에서의 프로세스와 스레드
멀티프로세싱 (Multiprocessing)
- 멀티프로세싱은 여러 CPU 코어에서 여러 프로세스를 동시에 실행하는 것을 의미한다. 각 프로세스는 독립적인 주소 공간을 가지므로 메모리 충돌이 없다.
- 주로 CPU 집약적 작업을 처리할 때 적합하다.
- 운영체제에서 멀티태스킹을 구현하는 방식이기도 하다. 예를 들어, 사용자는 한 화면에서 여러 프로그램을 동시에 실행할 수 있다.
멀티스레딩 (Multithreading)
- 멀티스레딩은 하나의 프로세스 내에서 여러 스레드를 병렬로 실행하는 것을 의미한다. 스레드 간의 메모리 공유로 인해, 성능 향상과 더 적은 자원 소모를 기대할 수 있다.
- 주로 I/O 집약적 작업에서 많이 사용된다. 예를 들어, 웹 서버는 수많은 요청을 동시에 처리해야 하기 때문에 스레드를 활용하여 비동기적으로 처리하는 것이 일반적이다.