멀티프로세스와 멀티스레드
이전에 저는 프로세스와 스레드에 대해서 포스팅한 적이 있는데요, 멀티 프로세스와 멀티 스레드에 대해 알기 이전에 아래 링크를 참고해주시면 좋을 것 같습니다! 😊
이름으로 유추할 수 있듯이 멀티 프로세스와 멀티 스레드는 여러개의 프로세스, 스레드가 동작하는 것을 일컫습니다.
단일이 아닌 다중으로 돌아감으로써 성능 향상 등 여러가지 효과를 얻을 수 있다. 하지만 또한 이로 인해 발생되는 부가적인 문제점도 발생하게 됩니다.
📌 멀티프로세스
멀티 프로세스는 운영체제에서 하나의 응용 프로그램에 대해 동시에 여러 개의 프로세스를 실행할 수 있게 하는 기술을 말합니다. 보통 하나의 프로그램 실행에 대해 하나의 프로세스가 메모리에 생성되지만, 부가적인 기능을 위해 여러개의 프로세스를 생성하는 것입니다.
프로세서 = 프로세스 ?
여기서, 프로세스와 프로세서는 엄연히 다른 개념입니다.
프로세스(process)는 프로그램의 실행 상태를 말하고, 프로세서(processer)는 CPU 코어를 일컫는다.
라고 하는데요, 이해하기 쉽게 말하자면 프로세서는 일을 처리하는 것으로 하드웨어적인 측면에서 보아야합니다.
즉, 일을 처리하는 주체라고 보면 됩니다. 반면 프로세스는 말 그대로 절차(과정) 입니다. 때문에 스스로 무언가를 처리하지 못합니다.
프로세스는 특정 목적을 수행하기 위해 나열된 작업의 목록 이라고 생각하여야 합니다. 즉 프로그램이죠. 컴퓨터에서 프로그램은 프로그래밍 언어로 코드 덩어리입니다. 이게 실행된다면 프로세스가 되는 것입니다. (즉, 프로그램이 실행되어 메모리에 적재된다면 프로세서가 된다.)
멀티 프로세스 내부를 보면, 하나의 부모 프로세스가 여러 개의 자식 프로세스를 생성함으로서 다중 프로세스를 구성하는 구조입니다. 한 프로세스는 실행되는 도중 프로세스 생성 시스템 콜을 통해 새로운 프로세스들을 생성할 수 있는데, 다른 프로세스를 생성하는 프로세스를 부모 프로세스(Parent Process) 라 하고, 다른 프로세스에 의해 생성된 프로세스를 자식 프로세스(Child Process) 라 합니다.
부모 프로세스와 자식 프로세스는 각각 고유한 PID(Process ID)를 가지고 있습니다. 부모 프로세스는 자식 프로세스의 PID를 알고 있으며, 이를 통해 자식 프로세스를 제어할 수 있게 됩니다. 또한, 자식 프로세스는 부모 프로세스의 PID와 PPID(Parent Process ID)를 알고 있어, 이를 통해 부모 프로세스와의 통신이 가능합니다.
다만, 통신이 가능할 뿐이지 프로세스와 자식 프로세스는 엄연히 서로 다른 프로세스로 독립적으로 실행되며, 독립적인 메모리 공간을 가지고 있어 서로 다른 작업을 수행하게 됩니다. 대표적인 예로 웹 브라우저의 상단 탭(Tab) 이나 새 창을 들 수 있습니다. 각 브라우저 탭은 같은 브라우저 프로그램 실행이지만, 각기 다른 사이트 실행하기 때문입니다.
이러한 브라우저 멀티 프로세스 원리를 확인하기 위한 간단한 실험도 가능합니다. 여러개의 탭을 띄운뒤, 하나의 탭에서 F12로 개발자 도구를 열고 콘솔 탭에 while(1) 무한 루프 코드를 실행 시켜본다고 가정해보겠습니다. 그러면 해당 탭에서는 클릭도 안되고 먹통이 되는 현상을 경험할 수 있습니다. 하지만 다른 탭에는 정상적으로 브라우징이 가능합니다. 이는 탭 마다 다른 프로세스로 동작하기 때문입니다.
📌 멀티프로세스의 장점
- 프로그램 안전성
멀티 프로세스는 각 프로세스가 독립적인 메모리 공간을 가지므로, 한 프로세스가 비정상적으로 종료되어도 다른 프로세스에 영향을 주지 않습니다. 그래서 프로그램 전체의 안전성을 확보할 수 있다는 장점이 있습니다.
예를 들자면 크롬 브라우저에서 여러개의 탭을 띄우고 여러곳의 웹사이트를 방문해 서비스를 이용한다고 하자. 이때 어느 한 탭의 웹사이트에서 무언가 잘못되어 먹통이 되었다면?
아주 심각한 오류가 아닌 이상, 당장 그 브라우저 탭의 웹사이트는 이용을 못하겠지만, 다른 탭은 별 문제없이 이용이 가능합니다. 이러한 이유는 자식 프로세스가 여러개 생성되어 메모리에 별도로 관리되기 때문입니다.
- 프로그램 병렬성
멀티 프로세스와 여러개의 CPU 코어를 활용하여 둘의 시너지를 합쳐, 다중 CPU 시스템에서 각 프로세스를 병렬적으로 실행하여 성능을 향상 시킬 수 있습니다. 예를 들어 이미지 처리나 비디오 인코딩과 같은 작업을 여러 개의 코어나 CPU에 분산시켜 빠르게 처리할 수 있습니다.
- 시스템 확장성
멀티 프로세스는 각 프로세스가 독립적이므로, 새로운 기능이나 모듈을 추가하거나 수정할때 다른 프로세스에 영향을 주지 않습니다. 그래서 시스템의 규모를 쉽게 확장할 수 있습니다.
만약 서버를 예시로 들자면, 대규모 웹 서비스에서는 수많은 요청을 동시에 처리하기 위해 여러대의 서버를 두고 로드 밸런서(Load Balancer)와 같은 장비를 사용하여 클라이언트 요청 트래픽을 분산 시킵니다. 이때 여러대의 서버는 컴퓨터를 여러개를 말하는 것일 수도 있고, 하나의 성능 좋은 컴퓨터에 여러개의 서버 프로세스를 두는 것을 말하기도 합니다. 멀티 프로세스의 상황은 후자입니다.
이렇게 멀티 프로세스를 사용하여 여러 대의 서버에 요청을 분산시켜 처리함으로써, 시스템의 규모를 쉽게 확장할 수 있으며, 부가로 서버의 장애나 다운타임을 최소화할 수 있게 되는 것입니다.
📌 멀티프로세스의 단점
- Context Switching Overhead (컨택스트 스위칭 오버헤드)
멀티 태스킹(multi tasking)Visit Website을 구성하는데 핵심 기술인 컨텍스트 스위칭(context switching) 과정에서 성능 저하가 올 수 있습니다. 특히나 프로세스를 컨텍스트 스위칭 하면, CPU는 다음 프로세스의 정보를 불러오기 위해 메모리를 검색하고, CPU 캐시 메모리를 초기화하며, 프로세스 상태를 저장하고, 불러올 데이터를 준비해야 하기 때문에, 이로 인한 빈번한 Context Switching 작업으로 인해 비용 오버헤드가 발생할 수 있게 됩니다. 반면 스레드를 컨텍스트 스위칭하면 프로세스 스위칭 보다 가벼워 훨씬 빠르고 좋습니다.
따라서, 멀티 프로세스 환경에서는 Context Switching Overhead를 최소화하는 방법이 중요합니다. 이를 위해서 프로세스 수를 적정하게 유지하거나, I/O 바운드 작업이 많은 프로세스와 CPU 바운드 작업이 많은 프로세스를 분리하여 관리하고, CPU
캐시를 효율적으로 활용하는 등의 방법을 고려해 봐야 한다고 합니다.
- 자원 공유 비효율성
멀티 프로세스는 각 프로세스가 독립적인 메모리 공간을 가지므로, 결과적으로 메모리 사용량이 증가하게 됩니다.
만일 각 프로세스간에 자원 공유가 필요할 경우 프로세스 사이의 어렵고 복잡한 통신 기법인 IPC(Inter-Process Commnuication)을 사용하여야 합니다.
IPC란 운영체제 상에서 실행 중인 프로세스 간에 정보를 주고받는 메커니즘을 말합니다. 이를 위해 파이프, 소켓, 메세지 큐 등 다양한 방법이 사용된다. 그런데 IPC 자체로 오버헤드가 발생합니다. 예를 들어, 파이프나 소켓과 같은 IPC 기법은 데이터를 복사하거나 버퍼링하는 과정에서 성능 저하가 발생할 수 있기 때문입니다. 또한 코드의 복잡도를 증가시킵니다.
📌 멀티 스레드
스레드는 하나의 프로세스 내에 있는 실행 흐름입니다. 그리고 멀티 스레드는 하나의 프로세스 안에 여러개의 스레드가 있는 것을 말합니다. 따라서 하나의 프로그램에서 두가지 이상의 동작을 동시에 처리하도록 하는 행위가 가능해집니다.
웹 서버는 대표적인 멀티 스레드 응용 프로그램입니다. 사용자가 서버 데이터베이스에 자료를 요청하는 동안 브라우저의 다른 기능을 이용할 수 있는 이유도 바로 멀티 스레드 기능 덕분인 것입니다. 즉, 하나의 스레드가 지연되더라도, 다른 스레드는 작업을 지속할 수 있게 됩니다.
멀티 프로세스와의 차이점을 부각시키기 위해, 멀티 프로세스를 설명할때 예시를 들었던 웹 브라우저를 다시 들어본다면...
멀티 프로세스는 웹 브라우저에서의 여러 탭이나 여러 창이라고 한다면, 대신 멀티 스레드는 웹 브라우저의 단일 탭 또는 창 내에서 브라우저 이벤트 루프, 네트워크 처리, I/O 및 기타 작업을 관리하고 처리하는데 사용된다고 보면됩니다.
🧐 좀 더 쉽게 이해하자면?
프로세서 > 프로세스 > 스레드 순으로 왼쪽으로 갈 수록 더 상위의 개념 (좀더 고차원적인?) 이라고 보면 됩니다.
프로세서는 일을 처리하는 주체이고, 프로세스는 달성하려는 목표, 그리고 스레드는 목표를 위한 자잘한 세부사항이라고 이해하면 될 것 같습니다.
예를 들어, 김치찌개를 만든다고 가정했을 때, 프로세스는 '김치찌개를 만드는 것' 이고, 스레드는 김치찌개를 만들기 위해서 양파를 자르기, 마늘 다지기, 물끓이기 등 이런 것들이라고 생각하면 됩니다. 멀티스레드인 경우는 그 자잘한 업무들을 동시에 처리할 수 있게 되고, 멀티 프로세스라면 김치찌개를 만드는 것 말고 된장찌개 만들기, 파스타 만들기, 이런식으로 이해하면 될 것 같습니다.
📌 멀티 스레드의 장점
윈도우, 리눅스 등 많은 운영체제들이 멀티 프로세싱을 지원하고 있지만 멀티 스레딩을 기본으로 하고 있습니다. 왜 멀티 프로세스 보다 멀티 스레드로 프로그램을 돌리는 것이 유리할까요?
- 스레드는 프로세스보다 가벼움
일단 스레드는 프로세스 보다 용량이 가볍습니다.스레드는 프로세스 내에서 생성되기 때문에 스레드의 실행 환경을 설정하는 작업이 매우 간단하여 생성 및 종료가 빠릅니다. 또한 스레드는 프로세스와 달리, 코드, 데이터, 스택 영역을 제외한 나머지 자원을 서로 공유하기 때문에 기본적으로 내장되어 있는 데이터 용량이 프로세스보다 당연히 작습니다. 그래서 스레드를 생성하고 제거할 때, 프로세스 내부의 자원만을 관리하면 되기 때문에 프로세스 생성, 제거 보다 훨씬 빠른 것입니다.
- 자원의 효율성
멀티 스레드는 하나의 프로세스 내에서 여러 개의 스레드를 생성되기 때문에, heap 영역과 같은 공유 메모리에 대해 스레드 간에 자원을 공유가 가능합니다. 이를 통해, 프로세스 간 통신 (IPC)을 사용하지 않고도 데이터를 공유할 수 있기 때문에, 자원의 효율적인 활용이 가능해 시스템 자원 소모가 줄어듭니다.
- Context Switching 비용 감소
스레드에도 컨텍스트 스위칭 오버헤드가 존재합니다. 하지만 상대적으로 프로세스 컨텍스트 스위칭 오버헤드보다 훨씬 낮아 비용이 낮다는 장점이 있습니다. 프로세스 컨텍스트 스위칭 비용은 스위칭할 때마다 CPU 캐시에 있는 내용을 모두 초기화하고, 새로운 프로세스 정보를 CPU 캐시에 적재해야 하므로 높은 비용이 듭니다. 반면, 스레드 컨텍스트 스위칭 비용은 스위칭할 때 스레드 간에 공유하는 자원을 제외한 스레드 정보(stack, register)만을 교체하면 되므로 프로세스 컨텍스트 스위칭 비용보다 상대적으로 낮은 것입니다.
📌 멀티 스레드의 장점
- 안정성 문제
멀티 프로세스 모델에서는 각 프로세스가 독립적으로 동작하므로 하나의 프로세스에서 문제가 발생해도 다른 프로세스들은 영향을 받지 않기 때문에 프로그램이 죽지 않고 계속 동작할 수 있습니다. 그러나 멀티 스레드 모델에서는 기본적으로 하나의 스레드에서 문제가 발생하면 다른 스레드들도 영향을 받아 전체 프로그램이 종료될 수 있습니다.
물론 이는 프로그래머의 역량에 따라 극복할 수 있긴 합니다. 예를들어 스레드에 에러가 발생할 경우 이에 대한 적절한 예외 처리를 잘 해놓는다던지, 에러 발생 시 새로운 스레드를 생성하거나 스레드 풀(Thread Pool)에서 잔여 스레드를 가져오던지 하여 프로그램 종료를 방지할 수 있습니다. 다만, 이때 새로운 스레드 생성이나 놀고 있는 스레드 처리에 추가 비용이 발생하게 됩니다.
- 동기화 문제
멀티 스레드 모델은 여러 개의 스레드가 공유 자원에 동시에 접근할 수 있기 때문에, 동기화 문제가 발생할 수 있습니다. 예를들어 여러 스레드가 동시에 한 자원을 변경해 버린다면 의도되지 않은 엉뚱한 값을 읽어 서비스에 치명적인 버그가 생길수도 있습니다. 따라서 스레드 간 동기화(syncronized)는 데이터 접근을 제어하기 위한 필수적인 기술입니다.
동기화 작업은 여러 스레드들이 자원에 대한 접근을 순차적으로 통제하는 것입니다. 그러면 동시 접근으로 인한 동시 수정과 같은 현상은 일어나지 않게 됩니다. 그러나 동기화 작업은 여러 스레드 접근을 제한하는 것이기 때문에 병목 현상이 일어나 성능이 저하될 가능성이 높다는 단점이 있습니다.
이를 해결하기 위해 임계 영역(Critical Section)에 대하여 뮤텍스(mutex), 또는 세마포어(Semaphore) 방식을 활용한다고 합니다.
활용 방식
- 임계 영역(Critical Section)- 멀티 스레드 프로그래밍에서 임계 영역은 공유 자원을 접근하는 코드 영역을 말한다. 대표적으로 전역 변수나 heap 메모리 영역이다.
- 뮤텍스(Mutex)- 공유 자원에 대한 접근을 제어하기 위한 상호 배제 기법 중 하나로, 임계 영역에 진입하기 전에 락(lock)을 획득하고, 임계 영역을 빠져나올 때 락을 해제하여 다른 스레드들이 접근할 수 있도록 한다. 한마디로 오직 1개의 스레드만이 공유 자원에 접근할 수 있도록 제어하는 기법이다.
- 세마포어(Semaphore)- 세마포어는 동시에 접근 가능한 스레드의 개수를 지정할 수 있다. 세마포어 값이 1이면 뮤텍스와 동일한 역할을 하며, 값이 2 이상이면 동시에 접근 가능한 스레드의 수를 제어할 수 있다. 스레드가 임계 영역에 진입하기 전에 세마포어 값을 확인하고, 값이 허용된 범위 내에 있을 때만 락을 획득할 수 있는 형식이다. 한마디로 뮤텍스 상위 호환 이라고 보면 된다.
- 그래도 Context Switching Overhead
앞서 멀티 프로세스보다 멀티 스레드의 컨텍스트 스위칭 오버헤드가 작아 성능에 유리하다라고 설명했었지만, 그래도 컨텍스트 스위칭 오버헤드 비용 자체를 무시할수는 없습니다. 특히나 스레드 수가 많으면 많을 수록 그만큼 컨텍스트 스위칭이 많이 발생되게 되고 당연히 이는 성능 저하로 이어집니다.
이 부분은 '스레드를 많이 쓸수록 항상 성능이 좋아질까?' 라는 물음으로 던질 수 있습니다. 보통 사람들이 생각하기에는 스레드가 많으면 많을 수록 그만큼 동시 처리수가 늘어나 당연히 스레드가 많으면 무조건 좋다고 이야기할 것입니다. 하지만 '컨텍스트 스위칭 오베허드'라는 개념을 알고 있는 개발자인 우리들은 '과연 꼭 그럴까?' 라는 의문을 던져야 합니다.
- 디버깅이 어려움
멀티 스레드를 사용하면, 여러 개의 스레드가 동시에 실행되기 때문에, 각 스레드의 동작을 추적하기 어려울 수 있다. 예를들어 코드를 디버깅하는 도중에 다른 스레드가 실행되어 예기치 않은 결과가 발생할 수 있습니다. 또한 어떤 스레드가 언제 어떤 자원에 접근하고, 어떤 순서로 실행되는지 등을 파악하기 어려울 수 있습니다.
따라서 스레드 간의 상호작용과 동기화 기법을 잘 이해하고, 디버깅 도구를 적극적으로 활용해야 합니다.
📌 총정리
각자 독립적인 공간이 있는 멀티 프로세스는 안정성이 높은 대신 유지하는데에 있어서 비용이 크다는 것을 알 수 있습니다. 또한 멀피 프로세스끼리 자원을 공유하는 것이 어렵습니다. 반대로 멀티 스레드는 서로 자원 공유가 가능하고, 이에 대한 비용이 멀티 프로세스보다는 비용이 적지만, 멀티 프로세스보다 안정성이 떨어지고 이런 안정성을 유지하기 위해 여러 후속 조치를 취해두어야합니다. 또한 자원 공유가 쉬운 대신, 에러가 발생했을 시, 디버깅이 어렵습니다.