IPC(Inter-Process Communication)
IPC란 여러 프로세스가 서로 데이터를 주고 받으면서 협력할 수 있도록 하는 방법이다.
프로세스는 독립적인 실행 객체이기 때문에 서로 통신하기 어렵다는 문제가 있다.
다른 프로세스간 통신을 하려면 커널단에서 제공되는 IPC를 사용해야 한다.
여러 IPC 방법들에 관해 알아보자!
1. Shared Memory
프로세스가 공유 메모리 할당을 요청하면 커널은 해당 프로세스에 메모리 공간을 할당해준다. 그 메모리를 어떤 프로세스건 커널의 관여 없이 접근할 수 있다.
장점
- 데이터 전송 속도가 빠르다.
단점
- 데이터를 읽어야 하는 시점을 알기 어렵다
- 쓰레드와 비슷하게 동시에 같은 메모리에 접근할 수 있기 때문에 동기화 문제를 해결해야 한다.
from multiprocessing import Process, Array def writer(shared_array): # 데이터를 공유 메모리에 쓰기 data = [1, 2, 3, 4] for i in range(len(data)): shared_array[i] = data[i] print("Writer Process: Data written to shared memory:", list(shared_array)) def reader(shared_array): # 공유 메모리에서 데이터 읽기 data = [shared_array[i] for i in range(len(shared_array))] print("Reader Process: Data read from shared memory:", data) if __name__ == "__main__": # 공유 메모리 생성 (정수형 배열, 크기 4) shared_array = Array('i', 4) writer_process = Process(target=writer, args=(shared_array,)) writer_process.start() writer_process.join() reader_process = Process(target=reader, args=(shared_array,)) reader_process.start() reader_process.join()
2. Message Passing
프로세스들이 커널을 통해 메시지를 주고받는 방식이다.
대표적인 방법으로 Pipe, Message Queue, Socket이 있다.
여기에는 두 가지 통신 방법이 있다.
- 직접 통신: 프로세스A가 자원을 커널의 메시지 큐로 전달하고 커널이 그 자원을 프로세스B에게 전달하는 방식
- 간접 통신: 프로세스A가 자원을 커널의 메시지 큐로 전달하고 프로세스B가 와서 자원을 읽어가게 하는 방식
장점: 높은 커널 의존성을 가지게 되므로 속도가 비교적 느리다.
단점: 운영체제를 통하기 때문에 자원에 대한 동기화 문제는 발생하지 않는다.
2-1. Pipe
단방향 or 양방향 데이터 스트림을 통해 프로세스 간에 데이터를 전달한다.
읽는 쪽과 쓰는 쪽이 정해져 있는 단순한 데이터 흐름에 적합하다.
“단방향 파이프 (Unnamed Pipe)”
echo "Hello from parent!" | cat
- 부모 프로세스가 자식 프로세스로 데이터를 단방향으로 전달한다
“양방향 파이프 (Named Pipe, FIFO)”
리눅스에서는 mkfifo
명령어를 사용하여 이름 있는 네임드 파이프인 FIFO를 만들 수 있다.
- FIFO 파일 생성
mkfifo /tmp/my_fifo
- 잠시 서버 역할을 할 shell에서 읽기/쓰기 작업
- FIFO에서 데이터를 읽고 그 내용을 출력한다
&
는 백그라운드에서 실행할 때 사용하는 것
cat < /tmp/my_fifo &
- 해당 shell에서 FIFO에 데이터를 써보자
echo "Hello from server!" > /tmp/my_fifo
- 클라이언트 역할을 할 shell에서 읽기/쓰기 작업
echo "Hello from client!" > /tmp/my_fifo cat < /tmp/my_fifo # 응답을 읽어보자!
데모
영상
2-2 Message Queue
프로세스 간의 메시지 교환을 큐를 통해 비동기적으로 수행할 수 있는 방법이다.
비동기: 메시지를 받는 프로세스가 즉시 응답하지 않아도 된고 메시지를 보낸 프로세스는 딴 작업을 계속할 수 있다
큐 = FIFO: 메시지를 보내는 순서대로 받는다는 것이 보장된다
여러 프로세스가 동일한 큐를 통해 메시지를 주고받을 수 있어서 다중 소비자/생산자 패턴을 쉽게 구현할 수 있다.
리눅스에서 메시지 큐는 msgget
, msgsnd
, msgrcv
등의 System Call을 사용한다. 파이썬에서 multiprocessing
모듈의 Queue
를 사용하여 동일한 기능을 시뮬레이션해보자.
import os import sysv_ipc import time # 메시지 큐 키값 <- 메시지를 식별하기 위해 필요 key = 128 def producer(): # 메시지 큐 생성 mq = sysv_ipc.MessageQueue(key, sysv_ipc.IPC_CREAT) for i in range(1, 6): message = f"구름 출석체크 {i}일차!" mq.send(message.encode()) # 메시지 전송 print(f"Producer sent: {message}") time.sleep(1) def consumer(): # 메시지 큐에 연결 mq = sysv_ipc.MessageQueue(key) while True: message, _ = mq.receive() # 메시지 수신 print(f"Consumer received: {message.decode()}") if message.decode() == "구름 출석체크 3일차!": # 바로 안 처리해도 ㄱㅊ! break print("딴거 작업!!") #time.sleep(3) while True: message, _ = mq.receive() # 메시지 수신 print(f"Consumer received: {message.decode()}") if message.decode() == "구름 출석체크 5일차!": break if __name__ == "__main__": pid = os.fork() if pid == 0: consumer() # 자식 프로세스는 소비자 역할 else: producer() # 부모 프로세스는 생산자 역할 os.waitpid(pid, 0) # 자식 프로세스가 끝날 때까지 대기
데모
영상
2-3 Sockets
네트워크를 통해 프로세스 간 통신을 지원하는 방법이다.
대표적으로 우리가 익숙한 서버-클라이언트 구조에서 사용된다.
당연히, 네트워크 상의 다른 시스템에 있는 프로세스와도 통신할 수 있다.
우리가 배우는 웹서버를 포함하여 SSH, DNS, 등의 서버가 소켓 위에 빌드된 대표적인 서비스이다.
(소켓 통신 코드는 생략!)
면접 질문 예상해보기
IPC에서 공유 메모리와 메시지 전달 방식의 차이점은 무엇이며, 각각의 장단점에 대해 설명해보세요
공유 메모리는 여러 프로세스가 동일한 메모리 영역을 공유하여 데이터를 주고받는 방식입니다. 이 방법은 데이터 전송 속도가 매우 빠르다는 장점이 있지만, 프로세스 간 동기화 문제를 해결하기 위해 세마포어와 같은 Lock을 사용해야 합니다. 동기화가 잘못되면 레이스 컨디션이나 데드락과 같은 문제가 발생할 수 있습니다.
메시지 전달 방식은 커널을 통해 프로세스 간에 메시지를 주고받는 방식으로, 파이프, 메시지 큐, 소켓 등이 있습니다. 이 방법은 프로세스 간의 간섭이 적어서 동기화 문제를 해결할 필요가 적지만, 데이터 전송 속도가 비교적 느릴 수 있습니다. 또한 커널을 경유하기 때문에 시스템 콜 오버헤드가 발생할 수 있습니다.
댓글
이 글 공유하기
다른 글
-
Terminal과 Shell
Terminal과 Shell
2022.04.08 -
Vim에서 Vundle 삭제하기
Vim에서 Vundle 삭제하기
2021.03.31 -
[WSL1/WSL2] 홈 디렉터리 위치 :: WSL 홈으로 Windows 파일 옮기기
[WSL1/WSL2] 홈 디렉터리 위치 :: WSL 홈으로 Windows 파일 옮기기
2020.05.16 -
[리눅스] apt, apt-get의 사용법 비교
[리눅스] apt, apt-get의 사용법 비교
2020.05.01
댓글을 사용할 수 없습니다.