한양과학기술고등학교

에듀테크 프로젝트 대시보드

IPC — 프로세스 간 통신

각 프로세스는 자신만의 독립된 가상 주소 공간을 갖는다. 그래서 프로세스 A의 변수를 프로세스 B가 그냥 읽을 방법이 없다 — 벽으로 나뉜 두 방과 같다. 이 벽을 넘어 데이터를 주고받으려면 커널이 중재하는 IPC(Inter-Process Communication) 메커니즘이 필요하다. 가장 익숙한 예가 셸에서 쓰는 | (파이프)다.

🔧 파이프(Pipe) 버퍼 체험 — ls | grep

커널 파이프 버퍼는 용량이 제한적이다(리눅스 기본 64KB). 소비자가 멈추면 생산자는 write()에서 블로킹된다.

🖥️ ls (생산자) stdout에 write()
커널 파이프 버퍼0 / 64 KB
0%
🔍 grep (소비자) stdin에서 read()

쓰기 버튼을 눌러 데이터를 파이프에 채워보세요.

⚖️ IPC 메커니즘 비교

막대는 상대적인 처리량 감(학습용 근사치, 실제 값은 하드웨어·구현체마다 다름)

공유 메모리
같은 물리 페이지를 두 프로세스의 주소 공간에 매핑. 커널 복사가 없어 가장 빠르지만, 동기화는 세마포어 등 별도 도구로 직접 해야 함.
처리량매우 빠름
파이프 / FIFO
write() → 커널 버퍼 복사 → read() → 유저 버퍼 복사, 총 2회 복사. 익명 파이프는 부모-자식 관계 필요, named pipe(FIFO)는 무관한 프로세스도 경로로 접근.
처리량빠름
메시지 큐
커널이 관리하는 메시지 우편함. 각 메시지에 타입을 붙여 선택적으로 수신 가능. 시스템 재부팅 전까지 프로세스가 죽어도 큐는 유지될 수 있음.
처리량보통
유닉스 도메인 소켓
같은 호스트 안에서만 쓰는 소켓. TCP 헤더·체크섬 같은 네트워크 오버헤드가 없어 TCP 소켓보다 빠름. Docker·nginx가 애용.
처리량보통~빠름
네트워크 소켓
유일하게 다른 컴퓨터의 프로세스와도 통신 가능. 프로토콜 스택(IP/TCP)을 거치므로 오버헤드가 가장 큼. 이 서버의 5개 FastAPI 앱도 결국 소켓으로 통신.
처리량느림
시그널
데이터가 아니라 이벤트 자체를 전달(예: SIGKILL). "무언가 일어났다"만 알릴 뿐 값을 실어 나르지 못함.
전달 정보량1비트 수준

💻 코드로 보는 IPC

셸의 파이프 한 줄이 내부적으로 어떤 시스템 호출과 대응하는지 비교

# ls의 stdout 파일 디스크립터를 grep의 stdin에 연결
ls -l /var/log | grep ".log" | wc -l

# 커널 내부: pipe() 시스템 콜이 fd 2개(읽기/쓰기)를 만들고
# 셸이 fork() 후 자식의 fd를 dup2()로 바꿔치기한다
from multiprocessing import Process, Pipe

def worker(conn):
    conn.send("안녕, 부모!")   # 커널 버퍼로 복사
    conn.close()

parent_conn, child_conn = Pipe()
p = Process(target=worker, args=(child_conn,))
p.start()
print(parent_conn.recv())   # 커널 버퍼에서 복사해 옴
p.join()
from multiprocessing import shared_memory
import numpy as np

# 두 프로세스가 "같은" 물리 메모리를 바라봄 → 복사 없음
shm = shared_memory.SharedMemory(create=True, size=1024)
arr = np.ndarray((128,), dtype=np.float64, buffer=shm.buf)
arr[:] = 0   # 다른 프로세스가 shm.name으로 열면 즉시 이 값이 보임

# 주의: 동시에 쓰면 경쟁 상태(race condition) 발생 → 락 필요
shm.close(); shm.unlink()

공유 메모리는 복사가 없는 대신 경쟁 상태 위험이 있고, 파이프·소켓은 복사 비용이 있는 대신 커널이 동기화를 대신 해준다 — 속도와 안전성은 트레이드오프다.