IPC와 스레드 메모리 공유 비교
NOTE
IPC와 스레드 메모리 공유는 “공유 방법”이 다른 게 아니라, 애초에 공유가 필요한 이유(주소 공간이 독립인가 아닌가) 자체가 다르다는 관점으로 정리한다.
1. 근본 원인: 주소 공간(Address Space)이 독립인가
| 프로세스 | 스레드 | |
|---|---|---|
| 주소 공간 | 프로세스마다 독립 — 서로 다른 가상 주소 공간 | 한 프로세스 안에서 공유 — 같은 가상 주소 공간 |
| 서로의 메모리 | 기본적으로 완전히 격리, 못 봄 | 이미 같은 공간을 보고 있음 |
| ”공유”하려면 | 커널이 중재하는 별도 채널 필요 → IPC | 아무것도 안 해도 됨 (포인터만 넘기면 됨) |
프로세스는 자기 안에서 쓰는 포인터 값이 다른 프로세스에겐 그냥 숫자일 뿐이다(같은 값이어도 그 프로세스의 가상 주소 공간에서 다른 물리 메모리를 가리키거나, 아예 매핑조차 안 되어 있음). 그래서 데이터를 넘기려면 커널이 개입해서 ①값을 복사해서 전달하거나(메시지 큐), ②아예 같은 물리 메모리를 양쪽의 가상 주소 공간에 매핑해주거나(공유메모리) 해야 한다. 이게 IPC가 필요한 이유의 전부다.
2. 프로세스 메모리 레이아웃과 스레드
한 프로세스의 메모리는 대략 이렇게 나뉜다.
높은 주소
┌─────────────┐
│ Stack │ ← 함수 호출/지역변수. 스레드마다 별도!
├─────────────┤
│ (빈 공간) │
├─────────────┤
│ Heap │ ← malloc()/free(). 스레드 간 공유
├─────────────┤
│ BSS │ ← 초기화 안 된 전역변수. 공유
│ Data │ ← 초기화된 전역변수. 공유
│ Code/Text │ ← 실행 코드. 공유
└─────────────┘
낮은 주소
한 프로세스 안에 스레드를 여러 개 만들면(pthread_create 등), Stack(+ 레지스터, TLS)만 스레드별로 독립이고 나머지(Code/Data/BSS/Heap)는 전부 그대로 공유된다. 그래서:
malloc()으로 할당한 heap 메모리는 어느 스레드가 만들었든 포인터 값만 다른 스레드에 넘겨주면 그대로 접근 가능 — 별도 IPC 메커니즘 필요 없음- 전역 변수도 마찬가지로 그냥 공유됨
3. 그래서 IPC는 뭘로 하나
같은 물리 서버 안에서도 프로세스가 다르면 메모리가 격리되므로, 목적에 따라 아래 두 계열을 다르게 쓴다.
| 방식 | 원리 | 특징 |
|---|---|---|
| 메시지 큐 | 커널이 데이터를 복사해서 전달 (송신 큐 → 수신 큐) | 이벤트/메시지 하나씩 주고받기에 적합 (송수신 타이밍이 분리되어도 큐에 쌓임) |
| 공유메모리 | 같은 물리 메모리를 여러 프로세스의 가상 주소 공간에 매핑 — 복사 없이 진짜로 같은 메모리를 봄 | 대용량 데이터를 여러 프로세스가 계속 zero-copy로 들여다봐야 할 때 적합 |
"공유메모리로 공유한다"는 절반만 맞은 이야기일 수 있다
사내 프레임워크가 메시지 큐 기반 IPC 래퍼와 공유메모리 기반 인메모리 DB(또는 레지스트리)를 동시에 제공하는 경우가 흔하다. 둘은 목적이 다르다 — 메시지 큐 기반 래퍼는 데이터를 복사해서 주고받고, 공유메모리 기반 컴포넌트는 복사 없이 여러 프로세스가 같은 메모리를 직접 본다. 어떤 컴포넌트를 쓰고 있는지에 따라 “공유”의 의미가 다르다는 걸 헷갈리면 안 된다.
4. 레이스 컨디션과 동기화 — 스레드 vs 프로세스
“메모리가 공유된다”는 곧 “동시에 건드리면 꼬일 수 있다”는 뜻이다. 공유가 자동이든(스레드) 명시적으로 연결했든(공유메모리 IPC) 이 문제는 똑같이 생기고, 해결책도 같은 계열이다 — 다만 이름 없는(스레드용) 버전과 이름 있는(프로세스용) 버전으로 나뉜다.
| 스레드 (같은 프로세스) | 프로세스 (공유메모리로 연결) | |
|---|---|---|
| 도구 | pthread_mutex, 이름없는 세마포어(sem_init, pshared=0) | System V 세마포어(semget), named 세마포어(sem_open) |
| 왜 다른 버전이 필요한가 | 뮤텍스 객체 자체가 그 프로세스의 heap/전역 메모리에 있으면 됨 — 어차피 다 같은 주소공간이라 포인터만 공유하면 접근 가능 | 뮤텍스 객체를 프로세스 A의 heap에 두면 프로세스 B는 그 주소를 아예 못 봄 → 커널이 관리하는 **전역 키(key)**로 식별해야 함 |
메시지 큐는 이 문제에서 조금 비켜나 있다 — 큐에 대한 송수신 자체(msgsnd/msgrcv)가 커널 안에서 원자적으로 처리되기 때문에, “메시지 큐에 동시에 넣었다가 꼬인다”는 걱정을 사용자가 직접 할 필요가 적다. 반면 공유메모리는 그냥 날것의 메모리라서, 그 안의 데이터 구조를 여러 프로세스가 동시에 건드리면 스레드 때와 똑같이 락이 필요하다.
5. 한눈에 정리
| IPC (프로세스 간) | 스레드 (프로세스 내) | |
|---|---|---|
| 근본 원인 | 주소 공간이 독립적 | 주소 공간이 이미 공유됨 |
| 공유 안 되는 부분 | 전부 (기본 완전 격리) | Stack, 레지스터, TLS만 |
| 데이터 주고받는 법 | 커널 매개 (메시지 큐 / 공유메모리 / 파이프…) | 포인터만 넘기면 끝 |
| 동시성 제어 | named 세마포어 등 (System V) | unnamed 뮤텍스/세마포어 (pthread) |