netpoll
Go 의 netpoll 은 Go 런타임 (runtime) 이 구현한 핵심 I/O 멀티플렉싱 메커니즘으로, Go 가 간결한 동기 프로그래밍 모델 (연결당 하나의 Goroutine) 로 고성능 네트워크 서비스를 구현할 수 있게 합니다. 본질은 운영체제底层 I/O 멀티플렉싱 시스템 호출 (예: Linux 의 epoll) 을 Go 의 Goroutine 스케줄러와 깊이 결합한 것입니다.
Netpoll 과 원본 GoNet 비교
Goroutine 수
- Go Net: 하나의 Goroutine 아래에 하나의 연결만 있습니다.
- NetPoll: 하나의 Goroutine 아래에 여러 연결이 있습니다.
Goroutine 컨텍스트 전환 스케줄링 부하
- Go Net: 고동시성 시 전환 부하가 큽니다. GoNet 은 연결당 하나의 goroutine 이므로 goroutine 수가 많으면 전환이 빈번합니다.
- NetPoll: 고동시성 시 전환 부하가 작습니다. NetPoll 은 여러 연결이 하나의 goroutine 을 공유하므로 전환 횟수가 적습니다.
메모리 소비
- Go Net: 메모리 소비는 연결 수와 양의 상관관계가 있으며 고동시성 시 Goroutine 수 폭주로 메모리 압박이 발생할 수 있습니다.
- NetPoll: 버퍼 풀을 미리 할당합니다.
트리거 방식
- Go Net: 엣지 트리거 ET 를 사용합니다. 한 번에 데이터를 모두 읽어 구현이 간단합니다.
- NetPoll: 레벨 트리거 LT 를 사용합니다. 데이터 버퍼용으로 충분히 큰 메모리를 미리 할당할 필요가 없습니다.
커널과 사용자 층 버퍼 풀 공유 지원 여부, 데이터 복사 한 번 감소
- Go Net: 지원하지 않습니다.
- NetPoll: 지원합니다. Buffer 풀을 관리하여 사용자에게 직접 전달하여 데이터 복사를 한 번 줄입니다.
적용 시나리오
- Go Net: 저동시성 단순 시나리오
- NetPoll: 고동시성 저지연 시나리오
netpoll 설계 아이디어
기본 아키텍처 원본 네트워크 라이브러리는 epoll lt 모드를 기반으로 개발되었으며 기본 아키텍처는 아래 그림과 같습니다.

goroutine 사용 poll 객체 풀이 존재하며 각 poll 객체는 하나의 epoll 을 가지고 있으며 별도로 하나의 goroutine 에 대응합니다. goroutine 수와 poll 객체 수가 일치하며 각 poll 객체는 여러 파일 디스크립터 fd 를 리스닝할 수 있습니다.
IO 읽기/쓰기 로직
- 각 poll 객체는 하나의 goroutine 을 시작하여 현재 epoll 의 읽기 가능, 쓰기 가능 등의 이벤트를 지속적으로 폴링합니다.
- 각 epoll 은 여러 fd 와 연관되며 각 fd 는 하나의 Buffer 와 연관됩니다.
- fd 에서 읽기 가능 이벤트를 감지하면 데이터를 Buffer 에 읽습니다.
- Buffer 의 데이터를 지속적으로 폴링하여 처리하며 데이터 읽기가 완료되면 후속 처리 로직의 코루틴 풀 GoPoll 에 실행을 알림합니다.
- 커널과의 시스템 호출 상호작용은 완전히 netpoll 이 제어하며 사용자 층의 Conn 읽기/쓰기는 공용 Buffer 를 조작할 뿐입니다.
장단점
- 장점: 고동시성 지원 능력이 더 강합니다.
- (1) Buffer 의 데이터를 폴링하여 처리하므로 데이터가 처리되지 못하는 상황이 발생하지 않습니다.
- (2) 하나의 goroutine 이 여러 연결에 대응하므로 연결이 매우 많더라도 리소스 스케줄링 전환 오버헤드가 크지 않습니다.
- (3) 사용자와 커널이 buff 를 공유하여 데이터 복사 작업을 한 번 줄여 효율을 향상시킵니다.
- 단점: 더 많은 메모리를 차지합니다.
- 장점: 고동시성 지원 능력이 더 강합니다.
