본문 바로가기

개발/CS

프로세스 간 통신(Inter-Process Communication, IPC)

반응형

운영체제에서 프로세스는 자신만의 가상 메모리를 할당 받아 이용하게 된다. 각 프로세스는 서로 다른 가상 메모리 영역으로 분리되어 있기때문에, 다른 프로세스의 가상 메모리에는 접근하지 못 한다. 그런데 프로세스 간 통신이 필요한 경우가 간혹 있다. 예를 들어, 포그라운드와 백그라운드가 분리되어 있는 경우에 그 둘은 데이터를 주고 받을 수 있어야 한다. 데이터베이스를 다루는 경우, 대개는 사용자로부터 SQL 쿼리를 입력받는 프론트엔드 프로세서와 실제 디스크 입출력으로 데이터를 처리하는 백엔드 프로세스가 분리되어 있다.

물론, 하나의 프로세스 내에서 스레드를 생성하여 활용할 수 있다. 스레드의 가장 큰 문제라고 하면, 한 스레드가 죽으면 프로세스 전체에 영향이 간다는 점이다. 아래 예시를 보자.

#include <iostream>
#include <mutex>
#include <thread>

using namespace std;

mutex my_mutex;

void my_thread(int id) {
  my_mutex.lock();
  cout << "my id is: " << id << endl;
  if (id == 5) {
    throw 1;
  }
  my_mutex.unlock();
}

int main() {
  thread threads[10];

  for (int i = 0; i < 10; i++) {
    threads[i] = thread(my_thread, i);
  }

  for (int i = 0; i < 10; i++) {
    threads[i].join();
  }

  return 0;
}

위 코드를 컴파일 하고 실행하면, i가 5일 때 생성된 스레드에서 예외가 발생하게 된다. 그럼 다른 스레드는 어떻게 될까?

$ ./threads_exception_test 
my id is: 0
my id is: 1
my id is: 2
my id is: 3
my id is: 7
my id is: 5
libc++abi: terminating due to uncaught exception of type int
[1]    82759 abort      ./threads_exception_test

프로세스 자체가 죽어버린다. 다른 모든 프로세스도 당연히 덩달아 죽어버리게 된다. 반면에, 아래 코드를 보자.

#include <unistd.h>

#include <iostream>

using namespace std;

int main() {
  pid_t pid;

  for (int i = 0; i < 10; i++) {
    pid = fork();
    if (pid > 0) {
      break;
    }
  }

  // 자식 프로세스일 때 예외 발생
  if (pid > 0) {
    throw 1;
  }

  cout << getpid() << endl;

  return 0;
}

부모 프로세스에서 자식 프로세스를 10개 fork하고 전부 예외를 발생시킨다. 그러면 부모 프로세스는 어떻게 될까?

$ ./processes_exception_test 
libc++abi: terminating due to uncaught exception of type int
libc++abi: terminating due to uncaught exception of type int
libc++abi: terminating due to uncaught exception of type int
libc++abi: terminating due to uncaught exception of type int
libc++abi: terminating due to uncaught exception of type int
libc++abi: terminating due to uncaught exception of type int
libc++abi: terminating due to uncaught exception of type int
libc++abi: terminating due to uncaught exception of type int
libc++abi: terminating due to uncaught exception of type int
83252
libc++abi: terminating due to uncaught exception of type int

부모 프로세스의 pid 값이 정상적으로 출력되는 것을 볼 수 있다. 즉, 스레드와 달리 자식 프로세스가 죽더라도 부모 프로세스에는 영향이 없다. 반대로 자식 프로세스는 그대로 두고 부모 프로세스를 죽였을 땐 어떻게 될까? 놀랍게도 자식 프로세스들은 잘 살아있는 모습을 볼 수 있다.

#include <unistd.h>

#include <iostream>

using namespace std;

int main() {
  pid_t pid;

  for (int i = 0; i < 10; i++) {
    pid = fork();
    if (pid == 0) {
      break;
    }
  }

  // 부모일 때만 예외를 발생
  if (pid > 0) {
    throw 1;
  }

  cout << getpid() << endl;

  return 0;
}
$ ./processes_exception_test_2 
83878
83879
83880
83881
83882
83883
83884
83885
83886
libc++abi: terminating due to uncaught exception of type int
83887
[1]    83877 abort      ./processes_exception_test_2

여담이지만 데몬 프로세스가 이런 방식으로 생성이 된다.


이처럼 개별 작업이 다른 작업에 영향을 받지 않기 위해서라도 멀티 프로세스 방식을 적용하곤 한다. 스레드 간 통신은 전역 변수를 활용하고 mutex나 semaphorer를 활용하여 손쉽게 접근함으로써 구현할 수 있다. 스레드는 같은 가상 메모리 공간을 공유하기 때문이다. 반면에, fork를 통해 생성된 자식 프로세스는 가상 메모리 공간이 아에 새로 할당되기 때문에 그러한 방식은 활용할 수 없다.

IPC에는 다양한 방식이 있다. 파일 시스템, 소켓, 파이프, 세마포어, 공유 메모리 등등

1. 파일 시스템을 활용한 IPC

 

반응형

'개발 > CS' 카테고리의 다른 글

Logistic Regression  (1) 2022.12.13
Bayesian DecisionTheroy  (1) 2022.12.12