개발을 하다보면 성능 개선을 위해 병렬 처리를 도입하려는 경우가 종종 발생한다. 이와 관련해 멀티 프로세스와 멀티 스레드가 많이 보이는 편인데 나 또한 크롤링 과정에서 이를 활용해 성능을 개선한 경험이 있었다. 그 당시에 관련 포스트를 읽으면서 멀티 프로세스는 CPU Bound에 유리하고, 멀티 스레드는 I/O Bound에 유리해 두 영역을 나누어 사용했을 때 효과가 있다는 것을 알았고 이를 적용해 소요 시간을 4시간에서 1시간을 줄이는 효과를 확인할 수 있었다.
문제는 각각의 방식이 해당 Bound에 유리한지를 명확하게 설명하지 못하는 것이다. 어렴풋이 그럴 것 같다는 생각은 들지만 보다 정확하게 이해하기 위해 OS 관련 책과 레퍼런스로 공부를 진행해보았다.
먼저 멀티 프로세스와 스레드를 알아보기 전, 프로세스와 스레드에 대해 이해하고 넘어갈 필요가 있다.
프로세스는 실행중인 프로그램의 인스턴스라고 볼 수 있으며, 운영체제에서 프로그램을 실행하는 작업 단위를 의미한다. 프로세스는 프로세스를 실행하기 위한 태스크(작업의 최소 단위)를 완료하기 위해서 다음과 같은 자원이 필요하다.
HDD와 같은 보조 기억 장치에 저장된 프로그램을 메모리로 로드하고 CPU가 메모리 위에 있는 프로그램의 명령어들을 CPU의 내부 레지스터로 가져온 다음에 실행하게 된다. 이를 기반으로 프로세스를 메모리 위에 적재된 프로그램이라 정의할 수 있다.
프로세스는 다음과 같은 구조를 가진다.
스레드는 프로세스 안에 또다른 작은 프로세스를 의미하며 CPU가 프로세스를 실행시기키 위한 최소 작업 단위이다. 한 프로세스 안에 포함된 스레드들은 프로세스의 자원을 공유하는 특징이 있다.
본론으로 돌아와서 멀티 프로세스가 왜 CPU Bound에서 유리한 이유는 프로세스가 CPU를 점유하는 것에서 시작한다. 누군가가 프로그램을 돌리기 위해 실행시킨다면 그 프로그램은 메모리에 로드되고 CPU 스케쥴러를 통해 CPU를 점유하게 된다. 하지먼 멀티 프로세스를 통해 하나의 프로그램을 여러 프로세스로 나누어 처리한다면 프로세스마다 CPU를 점유하기 때문에 하나의 프로세스에 비해 더 많은 CPU 점유를 통해 더 빠르게 CPU Bound를 해결할 수 있는 것이다. 최근 CPU의 코어 수가 점점 증가하고 있기 때문에 각각의 코어에 프로세스 연산 처리를 맡김으로써 프로세스를 동시에 실행할 수 있게 되는 것이다.
결과적으로는 하나의 프로세스를 여러 프로세스로 나눔으로써 CPU 점유율을 높여 CPU 연산량을 높이기 때문에 CPU Bound에 유리하다고 할 수 있다.
멀티 스레드의 특징은 스레드끼리 프로세스의 자원을 공유하는 특징으로 인해 멀티 프로세스에 비해 자원을 효율적으로 사용할 수 있다. 특히 멀티 프로세스의 경우 데이터를 공유하기 위해 IPC를 통해 통신을 진행하기 때문에 비용이 생기고, 하나의 프로세스가 완료될때까지 CPU를 점유할 수 있다는 보장이 없기 때문에 중간에 CPU 점유를 넘겨주게 되면서 Context Switching 오버헤드가 발생하게 된다. Context Switching의 경우 현재까지의 진행 상황을 PCB에 저장하고 다른 프로세스에 CPU 제어권을 넘겨주는 행위를 말한다.
스레드의 경우 Context Switching할 때 스레드 간에 공유하는 자원을 제외한 스레드 정보(stack, register)만을 교체하면 되지만, 프로세스는 CPU 캐시에 있는 내용을 모두 초기화하고, 새로운 프로세스 정보를 CPU 캐시에 적재해야 하므로 스레드에 비해 높은 비용이 발생하게 된다.
결과적으로 멀티 스레드는 멀티 프로세스에 비해 데이터 공유에서 발생하는 비용이 상대적으로 작으며 자원을 효율적으로 사용하기 때문에 멀티 프로세스보다 I/O Bound에 유리하다고 할 수 있다.
실제로 멀티 프로세스와 멀티 스레드를 도입하기 위해서는 현재 작업이 어떤 Bound에 걸쳐있는지를 확인하고 이를 분리해서 적용했을 때 효과가 있었다. 하지만, 성능이 개선된다고 무작정 도입하는 것은 문제가 될 수 있다. 멀티 스레드의 경우 스레드 하나에 문제가 발생하면 다른 스레드들도 영향을 받아 전체 프로그램이 종료될 수 있고, 여러 스레드가 하나의 공유 자원에 동시에 접근하면서 문제가 발생할 수 있는 등 다양한 문제가 발생할 수 있다. 멀티 프로세스도 마찬가지로 빈번한 Context Switching과 IPC로 인해 오버헤드가 발생하여 성능이 저하될 수 있다.
추가적으로 파이썬은 GIL로 인해 다수의 스레드가 동시에 파이썬 바이트코드를 실행시키지 못하게 막는다고 한다. 그러면 파이썬에서는 멀티 스레드가 쓸모없는 것이라 생각할 수 있지만 이는 CPU Bound에 한해서 작동하는 것이고, I/O Bound의 경우에는 정상적으로 작동한다.
Reference