2014년 5월 7일 수요일

멀티쓰레드 꼭 필요한가?

멀티쓰레드란 CPU 자원을 최대한 활용하기 위한 가장 효과적인 수단이다.
OS 차원에서 특정 쓰레드가 Idle 상태에 들어감과 동시에 다른 쓰레드에게 CPU 활용을 양보함으로써, 여러 트랜젝션을 병렬적으로 동시에 처리하는 것이 멀티쓰레드 주요 목적이다.

간단한 파일 업로드 서버의 경우, 각 트랜젝션(또는 세션)마다 쓰레드를 생성하고, 그 내에서 소켓을 읽어 파일에 쓰는 단순한 반복문을 넣어 주는 것만으로 손쉽게 멀티쓰레드 서버를 구현할 수 있다.
만약, 동시 접속을 몇 천개 이하로 제한하는 경우, 이것은 실제로 가장 손쉽고 가장 효율적인 서버를 구성하는 방법이다. 이 정도 시스템 상에서는 20ms 이상 길게 CPU를 사용하는 영역이 없기 때문에 컨텍스트 스위칭 부담이 전혀 없이 CPU를 100% 활용하는 것이 가능하다.

그러나, 처리 로직이 좀 더 복잡하고, 동시 접속 수가 더 크게 늘어나면 컨텍스트 스위칭 부담도 발생하고, 무엇보다 쓰레드 컨텍스트(스택)와 내부 데이터 처리를 위한 메모리 부담도 증가한다. 이는 트랜젝션 처리 속도에 영향을 미쳐 동시 접속 수가 더 많이 증가하는 악순환에 급격히 빠져 버릴 수 있다.
이러한 심각한 상황 발생을 막기 위해서는 쓰레드의 최대 개수를 제한하고 일정 부분 비동기 IO를 활용하는 수 밖에 없다. Windows의 IOCP ThreadPool 이 가장 대표적인 예라고 할 수 있다.

그렇다면, 아예 처음부터 전체 시스템을 비동기 IO만으로 구현하고 프로세스를 여러 개 띄우는 것이 더 나을 수도 있을까?
비동기 IO 냐, 멀티쓰레드냐를 떠나서 하나의 서비스를 여러 개의 프로세스로 나누게 되면 아래와 같은 문제가 발생한다.

1. 중앙집중적 관리.
동일한 서비스를 여러 개의 프로세스에서 가동하면 중앙집중적인 관리가 어려워진다.
로깅과 서비스 모니터링, 서비스의 변경 및 재시동 등 서비스 제어뿐 아니라 부하분산 등 분산처리 시스템 구성도 복잡해진다.
매우 제한적인 경우를 제외하곤 서버 당 (액티브한) 서비스는 하나만 가동되도록 설계하는 것이 옳바른 선택일 것이다.


2. 데이터 공유. (Buffering & Caching)
단일 프로세스 내에서의 데이터 공유는 해당 데이터의 포인터만 넘겨주면 된다. 트리나 배열처럼 공유하는 데이터의 양이 많거나 크기가 크더라도 단지 해당 포인터 하나만 넘겨주면 된다.
그러나 다중 프로세스 환경에서 데이터를 공유하려면 그 내용을 모두 전달해 주어야만 한다. 인코딩, 디코딩의 부담도 크지만, 해당 데이터가 서로 동기화되어야 한다면 그 처리는 매우 복잡해지게 된다. 특히, 복잡한 처리 절차를 반복하지 않고 미리 처리된 이전 결과를 즉시 반환하는 'Caching' 기법 사용에 많은 제약이 발생하게 되는데, 이러한 부분은 퍼포먼스에 큰 영향을 미칠 수 있다.

결론적으로 다중 코어 CPU를 사용하는 경우, 비동기 IO 만을 사용하여 멀티쓰레드 시스템과 동일한 퍼포먼스를 내는 것은 일반적으로 가능하지도 않고 그런 시도 자체가 바람직하지도 않다.

근래엔 일반 스마트폰 기기들도 멀티 코어 CPU를 장착하고 있다. 이제는 단일 코어를 사용하는 컴퓨터는 거의 없는 셈이다. 즉, 퍼포먼스가 중요한 프로그램이라면, 멀티쓰레드 사용은 필수적인 셈이다. 멀티쓰레드만이 하나의 프로세스가 CPU 다중 코어를 모두 활용할 수 있는 유일한 방안이기 때문이다.
멀티쓰레드를 사용하더라도 시스템 가용성을 최대화하기 위해서는 부분적인 비동기 IO 사용 또한 필수적이다. 이 두 가지는 조화롭게 사용되어야 한다.