feat. select, poll, epoll, iocp
하나의 통신 채널을 통해서 둘 이상의 데이터(시그널)를 전송하는데 사용되는 기술
물리적 장치의 효율성을 높이기 위해서 최소한의 물리적인 요소만 사용해서 최대한의 데이터를 전달하기 위해 사용되는 기술
입출력 다중화란 하나의 프로세스 혹은 스레드에서 입력과 출력을 모두 다룰 수 있는 기술을 말한다. Kernel 에서는 하나의 스레드가 여러 개의 소켓(파일)을 핸들링 할 수 있는 select, poll, epoll과 같은 시스템 콜(system call)을 제공(윈도우에서는 iocp, 맥에선 kqueue를 제공) 한다.
I/O멀티플렉싱 기법을 사용한다면, 각 클라이언트마다 별도의 프로세스나 스레드를 생성하는 것이 아닌 하나의 스레드에서 다수의 클라이언트에 연결된 소켓(파일 디스크립)을 관리하고 소켓에 이벤트가 발생할 경우에만 별도의 스레드를 만들어 해당 이벤트를 처리하도록 구현할 수 있다. 입출력 함수는 여전히 블록킹으로 작동하겠지만, 입출력 함수를 호출하기 전에 어떤 파일에서 입출력이 준비가 되었는지 확인할 수가 있다.
select 방식은 이벤트별로 감시할 파일들을 fd_set 이라는 fd 비트 배열에 등록하고, 등록된 fd에 어떠한 이벤트가 발생했을 경우 fd_set을 확인하는 방식으로 동작한다.
전통적인 방식으로, 대부분의 os가 지원하며 프로세스가 커널에 fd 상태를 직접 조회한다. 한 번에 다수의 fd를 조회하여 I/O 상태를 관찰하기 때문에 지속된 polling 필요 (CPU 낭비)하다.
한 번에 조회할 수 있는 fd의 수는 1024로 제한되며 timeout 사용 방식에 따라 blocking이 될 수도, non-blocking이 될 수도 있다.
커널에 의해서 완성되는 기능이 아닌, 함수에 의해 완성되는 기능이며, select 함수의 호출을 통해서 전달된 정보는 커널에 등록되지 않은 것이며 함수를 호출할 때마다 매번 관련 정보를 전달 해야 한다.
file descriptor를 하나 하나에 체크하기 때문에 O(n)의 계산량이 하다. 따라서 관리하는 file descriptor의 수가 증가하면 성능이 떨어진다.
poll이 여러 개의 fd를 다루는 방법은 select와 마찬가지로 이벤트를 기다리다가 이벤트가 발생하면, poll에서의 block이 해제되고, 다음 루틴에서 어떤 fd에 이벤트가 발생했는지 검사하는 방식을 사용한다.
기본적인 작동 방식은 select와 유사하나 fd의 개수 제한이 없고 select에 비해 os specific하여, 이식성이 상대적으로 나쁘다
접속수가 늘어나면 오히려 fd당 체크 마스크의 크기가 select는 3bit인데 비해, poll은 64bit 정도이므로 양이 많아지면 성능이 select보다 떨어진다.
epoll은 select 함수의 단점 극복을 위해 커널 레벨의 멀티플렉싱을 지원한다. 커널에 관찰대상에 대한 정보를 한 번만 전달하고, 관찰대상의 범위, 또는 내용에 변경이 있을 때만 변경 사항을 알려준다. 리눅스에서는 epoll, 윈도우에서는 IOCP, 맥에서는 Kqueue가 이에 해당
개선된 poll으로, 이벤트 기반으로 작동하며 관리 FD 개수 무제한이다. 커널이 FD의 상태를 직접 관리하며, 상태가 변경된 FD를 알려준다. 그렇기 때문에 커널로의 통신에 따른 오버헤드가 대폭 줄어든다.
epoll은 fd_set을 커널이 직접 관리하는 것으로 많은 부분이 개선되었다. 하지만 그 본질적인 동작 구조는 select와 비슷하다. 프로세스가 커널에게 지속해서 I/O 상황을 체크하여 동기화하는 개념은 여전히 같다. 따라서 epoll의 통지모델 역시 동기형 통지모델이다. 또한 timeout 개념이 select와 동일한 방식으로 동작하기 때문에 timeout에 들어온 인자가 어떠냐에 따라 blocking이기도 하고 non-blocking이기도 하다.
입출력을 담당할 포트를 지정하여 처리하겠다는 의미이며 입력과 출력의 완료 시점에서의 통지는 overlapped(중첩 입출력)에서 처리가 되므로, 이 기술은 윈도의 중첩 입출력 기술을 확장한 것으로 볼 수 있다. 내부에 큐를 두어, 입출력이 완료되었음을 큐에 저장하며 non-blocking. 백그라운드로 작동한다. async 방식으로, 콜백 사용이 가능하다.
입출력이 완료되면, 이 완료 보고는 입출력 완료 대기열에 쌓인다. 그러면 스레드를 깨우게 되고, 스레드는 대기열에 있는 완료 보고를 읽어서 데이터를 처리하는 식이다. 이렇게 적당한 개수의 워커 스레드를 만들어 두면, 운영체제가 쉬고 있는 워커 스레드를 깨울 수 있다.
이 방식은 스레드 혹은 프로세스 풀과 비슷해 보이지만 일반적으로 스레드 풀 방식에서는 쉬고 있는 스레드에 작업을 할당하기가 수월하지 않다. 이렇게 하려면 몇 가지 까다로운 기법을 사용해야 하는데, IOCP는 개발자가 신경 쓸 필요 없이 운영체제가 알아서 스레드를 선택해서 깨운다.
- I/O Multiplexing: The select and poll Functions
- https://www.slideshare.net/kslisenko/networking-in-java-with-nio-and-netty-76583794
- https://www.slideshare.net/JangHoon1/netty-92835335?from_action=save