[OS] 멀티 프로세스와 멀티 스레드, 둘 중에 뭐가 좋아?
제목의 답부터 말하자면 둘 중에 뚜렷하게 좋고 나쁨이 갈리는 게 아니다. 프로세스와 스레드 각각의 특성과 장단점이 있기 때문에 운영체제의 복잡성이나 프로그램의 특성 등을 고려해서 채택된다.
1. 프로세스와 스레드의 특징 차이
프로세스란 실행 중인 프로그램으로, 1개의 CPU가 한 번에 한 프로세스만을 실행할 수 있고 프로세스 간 데이터 접근이 불가하다는 특징이 있다. 스레드란 한 개의 프로세스 안에 여러 개의 프로세스를 동시에 생성 및 실행 가능한 구조다. 프로세스 안에서는 모든 스레드끼리 데이터에 접근이 가능하다. 멀티프로그래밍을 구현할 때 CPU가 어떻게 콘텍스트 스위칭을 하는지 이전 글에 정리해 두었다. 프로세스를 한꺼번에 여러 개 실행하는 것처럼 보이기 위해 CPU는 레지스터의 도움을 받아서 사용자가 느끼지 못할 정도로 빠른 속도로 프로세스를 바꾸어 가며 실행한다. 스레드는 그럴 필요가 없다. 한 프로세스 내에서 여러 프로그램을 실행 가능한 구조이기 때문이다.
2. 멀티 프로세스와 멀티 스레드의 구조 비교
앞서 프로세스의 구조를 정리한 글에서처럼 프로세스에는 data, files, 레지스터, 스택, 코드 등이 구성돼 있다. 스레드도 프로그램을 실행하기 위해 각각의 요소가 필요하다. 그럼 프로세스가 여러 개 있는 멀티 프로세스와, 스레드가 여러 개 있는 멀티 스레드는 어떻게 다른지 모식도로 나타내 보았다.
멀티 프로세스와 달리, 멀티 스레드는 한 개의 프로세스에서 heap, data, code를 공유하면서 병렬 처리를 할 수 있고, 각 스레드마다 stack 영역과 PC/SP를 따로 가진다.
이전 글에서는 초기 컴퓨터에서처럼 CPU가 1개인 경우를 가정하고 정리해 봤는데 최근에는 가성비 노트북도 기본으로 멀티코어, 쿼드코어를 탑재하고 있기 때문에 멀티 프로세싱이 가능하다. 이전까지 1개의 CPU가 여러 개의 프로세스를 번갈아가며 수행하는 것을 '멀티 태스킹'이라고 하고, 최신 컴퓨터에서처럼 CPU가 여러 개 있는 환경에서는 여러 CPU가 여러 개의 프로세스를 협력해서 수행하는 것을 '멀티 프로세싱'이라고 한다. 멀티 프로세싱이 가능한 건 스레드를 여러 개 만들면 가능하다. 이를 그림으로 나타내면 다음과 같다.
3. 프로세스 간, 스레드 간 자원 공유 방식
먼저 프로세스를 살펴보면, 프로세스 간에는 커뮤니케이션이 원칙적으로 제한되어 있다. 프로세스마다 독립적인 메모리 공간을 가지고 있고, CPU가 콘텍스트 스위칭을 수시로 하는데, 한 프로세스에서 다른 프로세스의 메모리에 접근해서 값을 바꾸거나 주소를 변경해버리면 해킹이나 다름없는 것이다. 그럼에도 불구하고 별도의 통신이 필요한 때가 있는데, 각각 프로세스의 상태 값이나 데이터가 필요할 때 IPC(Inter-Process Communication)이라는 통신을 활용한다. IPC에는 몇 가지 대표적인 방법이 있다.
1. file 공유하기 - 프로세스 간 저장매체는 공유하므로 사용 가능하나, 시스템콜 등등 거치기에는 실시간으로 업데이트하기 어렵다.
2. 메시지 큐 - 키값을 가지고 양방향 통신을 함
3. shared memory - 커널에 메모리를 하나 파고, 읽고 쓰는 변수를 하나 지정함
4. 파이프 - fork()해서 생성한 부모/자식 관계에 있는 프로세스의 경우에 단방향으로 통신하여 사용 가능하다.
5. signal - 커널 모드에서 사용자모드 전환 시, 한 프로세스에서 다른 프로세스에 이벤트를 전달한다.
6. 세마포어 - 스레드에서 임계영역에 대한 접근을 일시적으로 락하기 위한 메커니즘
7. 소켓 - 네트워크 통신에 사용되는 방법이다.
1번은 파일에 직접적으로 접근하기 때문에 통신을 위해서 활용하기에는 시간 효율이 떨어진다. 2번부터 7번까지는 커널 영역을 이용하는 방법이다. 각 프로세스는 커널 영역인 3GB~4GB 공간의 물리 메모리를 공유하기 때문에 이를 통신에 활용하는 것이다. 반면 스레드는 각 스레드 간의 통신을 위해 IPC가 필요가 없다. 자원 공유 효율이 높다는 것이다.
4. 스레드의 장단점
스레드의 특징을 알아보았는데, 멀티 스레드는 빠르게 프로세스를 병렬처리 할 수도 있고, 자원을 효율적으로 공유하면서 통신도 수월하게 할 수 있어 스레드를 여러 개 만들어서 프로그램을 실행하는 게 항상 좋을 것 같다는 생각이 들 수 있다. 그렇지만 스레드도 장단점이 뚜렷하여 원하는 의도에 따라서 멀티 프로세스 방식을 채택해야 할 때도 있다.
장점 1. 사용자의 응답성이 향상된다.
프로세스끼리 통신을 할 때는, 커널 영역에 들어가서 시스템 콜을 하고 응답을 받아 전달을 하는 데 시간이 걸리는데, 스레드끼리는 응답 속도가 향상된다. 일례로 클라이언트에서 서버로 요청을 할 때, 여러 개의 스레드로 동시에 응답을 처리하여 사용자 경험을 높일 수 있는 것이다.
장점 2. 자원 효율이 높다.
프로세스는 각각 4GB의 자원을 가지고 실행이 되는데, 스레드는 4GB를 서로 공유하면서 동시에 실행된다.
단점 1. 멀티스레드의 경우 한 스레드만 문제가 생겨도, 전체 프로세스가 영향을 받는다.
멀티 프로세스의 경우, 한 프로세스에 문제가 생겨도 다른 프로세스의 실행에는 영향이 없다.
단점 2. 스레드를 많이 생성하면 성능이 저하된다.
모든 스레드를 스케줄링한다면, 수많은 콘텍스트 스위칭이 일어날 때 성능이 저하되는 것은 당연한 말이다.
단점 3. 여러 스레드가 동일한 자원에 접근하기 때문에 동기화 이슈 발생 가능하다.
동기화란 작업들 사이에 실행 시기를 맞추는 것인데, 동일한 자원에 대하여 동시에 작업이 들어가는 경우 각 스레드의 결과에 영향을 미친다. 이를 해결하기 위해 임계 영역에 대하여 뮤텍스(mutex), 또는 세마포어(Semaphore) 방식을 활용한다.
5. 뮤텍스와 세마포어 방식 비교
임계 영역(critical section)에 대한 접근을 제한하기 위해, locking 메커니즘이 필요하다. 그 방법으로 뮤텍스와 세마포어 방식이 있다.
lock.acquire()
// 임계 영역
lock.release()
1. mutex - 임계 구역에 1개의 스레드만 들어갈 수 있다. 이는 성능상의 이슈가 있을 수 있다.
2. semaphore - 임계 구역에 여러 스레드가 들어갈 수 있고, counter를 두어서 허용 가능한 스레드를 제한한다.
뮤텍스는 임계 영역을 들어갈 때 락을 걸어, 다른 프로세스 또는 스레드가 접근하지 못하도록 하고, 락을 획득(acquire)한 그 스레드가 임계 영역을 나올 때 해제(release) 해야 한다. 세마포어는 정수를 초기값으로 가지는데, 그 수만큼 여러 스레드가 임계 영역을 동시에 접근 가능하다. 뮤텍스와 달리, 현재 수행 중인 스레드가 아닌 다른 스레드가 세마포어를 해제할 수 있다.
스레드가 임계 영역에 진입하는 데 문제가 생기는 경우도 있다. 교착상태(Deadlock)와 기아상태(Starvation)이다. 교착 상태는 스레드가 임계 영역에 진입하지 못하고 무한대기 상태로 멈추는 경우다. 당연히 프로그램이 그동안 동작하지 못하고, 서로 작업이 끝날 때까지 기다리기만 하는 문제다. 기아 상태는 스레드의 우선순위가 낮아서 원하는 자원을 할당받지 못하는 경우다. 여러 스레드가 부족한 자원을 위해 경쟁할 때 발생하는 문제다. 이런 경우에는 오래 기다린 프로세스의 우선순위를 높인다던지, 또는 FIFO 방식으로 변경하던지 하는 방법으로 우선순위를 변경하여 해결한다.