블로킹·논블로킹 I/O와 이벤트 기반 입출력 모델

블로킹 I/O, 논블로킹 I/O, 멀티플렉싱과 비동기 I/O의 대기 방식·확장성·운영 선택 기준을 정리한다.

2026-08-14 · 최초 발행 2026-01-16

입출력 대기는 서버 구조를 바꾼다

디스크 읽기·쓰기, 네트워크 통신, 사용자 입력은 모두 I/O에 속한다. 이 작업은 CPU 연산보다 수백~수천 배 느리고, 외부 장치 상태에 따라 완료 시점을 예측하기 어렵다. 애플리케이션은 시스템 콜을 통해 커널에 I/O를 요청하며, 호출한 실행 흐름을 언제 멈추게 할지에 따라 응답성과 처리량이 달라진다.

I/O는 보통 두 단계로 진행된다.

장치/네트워크커널애플리케이션장치/네트워크커널애플리케이션단계 1: 데이터 준비 대기단계 2: 커널→사용자 복사시스템 콜 (read/write)데이터 요청데이터 전송데이터 반환

먼저 커널은 장치에서 데이터를 받아 커널 버퍼에 넣는다. 네트워크라면 패킷 도착을, 디스크라면 섹터 읽기를 기다리는 구간이다. 이후 커널 버퍼의 데이터를 사용자 공간 버퍼로 복사한다. 이 복사는 DMA 또는 CPU로 수행하는 비교적 빠른 메모리 복사 작업이다.

호출이 끝날 때까지 기다리는 블로킹 I/O

블로킹 I/O에서는 read() 같은 시스템 콜이 완료될 때까지 호출 프로세스나 스레드가 대기 상태가 된다. 데이터가 준비되고 사용자 공간으로의 복사까지 끝나야 호출이 반환되므로, 그동안 같은 실행 흐름은 다른 일을 하지 못한다.

디스크/네트워크커널프로세스디스크/네트워크커널프로세스블로킹 (대기 상태)데이터 준비 대기커널 버퍼→사용자 버퍼 복사실행 재개read() 시스템 콜데이터 요청데이터 전송read() 반환

순차적인 제어 흐름이라 구현과 디버깅이 단순하고 데이터 무결성을 보장하기 쉽다. 반면 I/O가 진행되는 동안 프로세스는 유휴 상태가 되고 CPU 활용률이 떨어질 수 있다. 동시 처리를 늘리려면 다중 스레드나 프로세스가 필요하며, 그에 따른 컨텍스트 스위칭 오버헤드도 생긴다.

간단한 파일 읽기는 이 방식으로 자연스럽게 작성할 수 있다.

// 간단한 파일 읽기 (블로킹)
fd = open("file.txt", O_RDONLY);
bytes_read = read(fd, buffer, size);  // 완료까지 블로킹
process(buffer);
close(fd);

간단한 CLI 도구, 순차적 배치 처리, 동시 연결 수가 적은 서비스, 빠른 개발이 필요한 경우에는 블로킹 I/O가 적합하다.

즉시 제어권을 돌려주는 논블로킹 I/O

논블로킹 I/O는 요청한 뒤 호출을 즉시 반환한다. O_NONBLOCK 플래그로 read()를 호출했을 때 데이터가 없으면 EAGAIN 또는 EWOULDBLOCK을 받고, 데이터가 있다면 읽은 결과를 바로 받는다. 호출자는 기다리는 대신 다른 작업을 수행하거나 다시 상태를 확인한다.

디스크/네트워크커널프로세스디스크/네트워크커널프로세스다른 작업 수행다른 작업 수행read() (Non-blocking)EAGAIN (데이터 없음)read() (Non-blocking)EAGAIN (데이터 없음)데이터 도착read() (Non-blocking)데이터 반환

I/O를 기다리는 동안에도 프로세스가 다른 작업을 할 수 있어 단일 스레드로 여러 I/O를 다룰 수 있다. CPU 활용률과 응답성 측면에서 이점이 있지만, 상태 관리가 필요하고 디버깅 난이도도 높아진다.

준비 여부를 계속 확인하는 순수 폴링은 빈 루프를 만들 수 있다.

// 비효율적인 폴링
while (true) {
    result = read(fd, buffer, size);  // Non-blocking
    if (result > 0) {
        process(buffer);
        break;
    }
    // CPU 낭비되는 빈 루프
}

이 문제 때문에 논블로킹 I/O는 보통 I/O 멀티플렉싱과 함께 사용한다.

하나의 이벤트 루프로 여러 채널을 감시하는 방식

I/O 멀티플렉싱은 하나의 스레드가 여러 I/O 채널의 준비 상태를 감시하고, 이벤트가 발생한 채널을 처리하는 방식이다. select, poll, epoll 같은 시스템 콜이 이 역할을 맡는다.

다중 I/O 채널I/O 멀티플렉서단일 쓰레드이벤트이벤트이벤트이벤트이벤트 루프select/poll/epoll소켓 1소켓 2소켓 3파일 FD

select는 가장 오래된 멀티플렉싱 API다. 최대 FD 수가 일반적으로 1024로 제한되고, 매 호출마다 전체 FD 집합을 커널에 전달한 뒤 선형 스캔으로 준비된 FD를 확인한다. FD 수가 늘수록 O(n) 성능 저하와 집합 복사 오버헤드가 나타난다.

pollpollfd 구조체 배열을 사용해 select의 FD 제한을 해소하지만, 여전히 전체 배열을 전달하고 선형 스캔해야 한다.

Linux의 epoll은 이벤트 기반 알림을 제공하는 고성능 멀티플렉싱 방식이다. epoll_create()로 인스턴스를 만들고, epoll_ctl()로 FD를 등록·수정·삭제한 뒤, epoll_wait()로 이벤트를 기다린다. 커널이 FD 목록을 유지하며 준비된 FD만 반환하므로 대규모 동시 연결에 효율적이다. 엣지 트리거(ET)와 레벨 트리거(LT) 모드도 제공한다.

BSD와 macOS에서는 kqueue가 유사한 역할을 한다. 고성능 이벤트 알림을 제공하며 다양한 이벤트 타입을 지원한다.

완료 통지를 받는 비동기 I/O

비동기 I/O는 요청 직후 반환한 뒤, 작업 완료 시 콜백이나 시그널로 결과를 알린다. 논블로킹 I/O처럼 요청자가 멈추지 않지만, 완료 여부를 폴링할 필요가 없다는 점이 다르다.

디스크커널프로세스디스크커널프로세스다른 작업 수행사용자 버퍼에 복사결과 처리aio_read() 요청즉시 반환데이터 요청데이터 전송시그널/콜백으로 완료 알림

POSIX AIO는 aio_read()aio_write()로 비동기 요청을 보내고, aio_error()로 상태를 확인하며, aio_return()으로 결과를 얻는다. 다만 구현 품질에는 차이가 있고, 일부 시스템에서는 스레드 풀로 에뮬레이션된다. 네트워크 I/O 지원도 제한적이다.

Linux 5.1 이상의 io_uring은 파일과 네트워크를 모두 지원하는 진정한 비동기 I/O 구조다. 요청을 제출하는 SQ(Submission Queue), 완료 결과를 받는 CQ(Completion Queue), 커널과 사용자 공간이 공유하는 메모리로 구성되며 고성능·저지연을 목표로 한다.

Windows에서는 IOCP가 완료 포트(Completion Port)를 기반으로 고성능 비동기 I/O를 제공한다. 스레드 풀을 자동으로 관리하며 대규모 서버에 최적화되어 있다.

대기 구간과 확장성의 차이

특성 Blocking Non-Blocking I/O 멀티플렉싱 비동기 I/O
대기 방식 완료까지 블록 즉시 반환, 폴링 이벤트 대기 콜백/시그널
단계 1 블록 아니오 아니오
단계 2 블록 아니오
구현 복잡도 낮음 중간 중간~높음 높음
확장성 낮음 중간 높음 매우 높음
CPU 효율 낮음 낮음~중간 높음 매우 높음

블로킹 모델은 요청마다 스레드를 두고 각각의 I/O 완료를 기다리는 구조로 이어지기 쉽다. 반대로 논블로킹 I/O와 멀티플렉싱을 결합하면 단일 이벤트 루프가 여러 요청과 I/O 채널을 관리한다.

Non-Blocking + Multiplexing요청 1단일 이벤트 루프요청 2요청 3epoll/kqueueI/OI/OI/OBlocking I/O요청 1쓰레드 1요청 2쓰레드 2요청 3쓰레드 3I/OI/OI/O

서버와 데이터 처리 구성에서의 선택

Apache의 쓰레드 풀과 블로킹 I/O 구조는 요청당 스레드를 사용하므로 구현은 단순하지만, 메모리 사용량이 높고 동시 연결에 제한이 생긴다. Nginx의 이벤트 기반 논블로킹 I/O 구조는 단일 또는 소수 스레드에서 epoll이나 kqueue를 사용해 높은 동시 연결을 처리하며 메모리 효율을 높인다.

Node.js는 단일 스레드 이벤트 루프와 libuv 기반 비동기 I/O를 사용하며, 콜백·Promise·async-await로 높은 동시성을 다룬다. Redis 역시 단일 스레드와 이벤트 루프, epoll을 사용하고 인메모리 연산으로 I/O를 최소화해 초고속 처리를 지향한다.

데이터베이스에서는 연결당 스레드를 사용하는 전통적 구조 외에, 스레드 풀과 비동기 I/O를 조합하는 구조가 쓰인다. AIO는 디스크 I/O 최적화에 활용할 수 있다.

고성능 네트워크 서버, 대규모 동시 연결, 웹 서버·프록시·채팅 서버처럼 리소스 효율이 중요한 환경에서는 논블로킹 I/O와 멀티플렉싱 조합이 맞다. 디스크 I/O가 집약적이거나 저지연과 최고 성능이 요구되는 고급 시스템 프로그래밍에는 비동기 I/O를 고려할 수 있다.

운영체제블로킹 I/O논블로킹 I/O이벤트 루프epoll비동기 I/O