앞선 글에서는 select, poll, epoll을 중심으로 Reactor 패턴 기반의 I/O 멀티플렉싱을 살펴보았다.
Reactor 모델에서는 커널이 “지금 I/O를 수행할 수 있다”는 준비 상태(ready) 를 알려주고, 실제 read() / write() 호출은 애플리케이션이 직접 수행한다는 특징이 있었다.
하지만 모든 운영체제가 이 방식만을 사용하는 것은 아니다.
특히 Windows 환경에서는 전혀 다른 접근 방식을 취하는데, 바로 Proactor 패턴이다. 이번 글에서는 Reactor와 대비되는 개념으로서 Proactor 패턴, 그리고 그 대표적인 구현체인 IOCP(I/O Completion Port) 에 대해 살펴본다.
Proactor 패턴이란?
Proactor 패턴의 핵심은 한 문장으로 정리할 수 있다.
“I/O를 애플리케이션이 직접 수행하지 않고, 커널에게 맡긴다.”

겉으로 보면 Reactor 패턴과 Proactor 패턴은 서로 비슷해 보일 수 있다. 두 모델 모두 I/O 작업의 시작은 User Space에서 요청된다는 공통점을 가지기 때문이다. Reactor에서는 커널에 감시할 소켓을 등록해 두고, 커널이 “이제 읽거나 쓸 수 있다”는 신호를 보내주면, 실제 read()나 write() 호출은 결국 사용자 코드가 수행한다.
Proactor 역시 I/O 작업의 요청은 User Space에서 시작된다. 하지만 여기서부터 두 패턴의 핵심적인 차이가 드러난다. Reactor에서 커널의 주된 역할은 소켓이 읽기/쓰기 가능한 상태가 되었음을 알려주는 것이라면, Proactor에서 커널의 역할은 I/O 작업을 직접 수행하고 그 완료 결과를 통지하는 것이다.
즉, Reactor는 “이제 실행해도 된다”는 상태를 알리는 모델이고, Proactor는 “이미 실행을 끝냈다”는 결과를 알리는 모델이라고 볼 수 있다. 이 차이로 인해 두 패턴은 I/O 실행의 책임이 어디에 있는지, 그리고 이벤트가 의미하는 바가 무엇인지에서 본질적으로 다른 구조를 가진다.
Reactor에서도 read()를 호출하면 결과를 반환받지 않나?
맞다. Reactor 패턴에서도 read()를 호출하면 동일하게 결과를 반환받는다. 하지만 중요한 점은, 이 read() 호출과 결과 처리는 Reactor의 역할이 아니라 Handler의 역할이라는 것이다.
Reactor는 어디까지나 “어떤 소켓이 지금 실행 가능한 상태인지”를 감지하고 이를 통지하는 역할만 담당한다. 실제로 read()나 write()를 호출하고, 그 결과를 처리하는 로직은 Reactor 구조 안에 포함된 Handler(사용자 코드)가 수행한다. 이 때문에 Reactor 패턴에서는 I/O 실행 자체가 User Space의 책임으로 남아 있으며, 커널은 실행 가능 여부만을 알려주는 역할에 머무르게 된다.
IOCP는 왜 Proactor인가?
Windows의 IOCP(I/O Completion Port) 는 Proactor 패턴을 대표하는 구현이다.
IOCP 기반 서버의 흐름을 단순화하면 다음과 같다.

- 애플리케이션이 비동기 I/O 작업을 커널에 요청한다.
- 커널이 백그라운드에서 실제 I/O 작업을 수행한다.
- I/O 작업이 완료되면, 커널은 완료 이벤트를 Completion Port에 전달한다.
- 워커 스레드는 Completion Port에서 완료 이벤트를 받아 후처리를 수행한다.
여기서 중요한 점은, 애플리케이션이 read() / write()를 직접 호출하는 시점이 없다는 것이다.
이미 요청 단계에서 I/O 작업 자체가 커널로 넘어가 있으며, 애플리케이션은 “완료 통지”만을 처리한다.
이러한 이유로 IOCP는 전형적인 Proactor 모델로 분류된다.
Reactor vs Proactor 정리하기
두 패턴의 차이를 핵심만 놓고 보면 다음과 같다.
| 구분 | Reactor | Proactor |
| 이벤트 의미 | I/O 가능(ready) | I/O 완료(completion) |
| 실제 I/O 수행 | 애플리케이션 | 커널 |
| 대표 구현 | select / poll / epoll | IOCP |
| 이벤트 후 동작 | read/write 호출 | 결과 처리 |
| 모델 성격 | 동기적 I/O + 비동기 통지 | 비동기 I/O + 완료 통지 |
이 차이 때문에 Reactor 패턴에서는 non-blocking I/O, ET/LT 같은 개념이 중요해졌고,
Proactor 패턴에서는 I/O 요청 관리와 완료 이벤트 처리가 핵심이 된다.
왜 이런 차이가 생겼을까?
Reactor와 Proactor의 차이는 단순한 API 설계나 성능 선택의 문제가 아니다.
이는 각 운영체제가 I/O를 바라보는 관점, 즉 운영체제 설계 철학의 차이에서 비롯된 것이다.
Unix / Linux 계열의 관점
Unix 계열 운영체제는 전통적으로 다음과 같은 철학을 가진다.
“커널은 최소한의 역할만 수행하고, 제어는 사용자 공간에 맡긴다.”
이 철학에 따라 커널은 소켓이 읽기/쓰기 가능한 상태인지 감시하고 그 사실을 애플리케이션에 통지하는 역할까지만 담당한다. 실제 I/O 실행(read(), write())과 데이터 처리 흐름은 애플리케이션이 직접 제어한다.
이러한 구조는 select, poll, epoll로 이어지는 Reactor 중심의 설계로 자연스럽게 발전했다.
Windows 계열의 관점
반면 Windows는 다른 방향의 철학을 선택했다.
“커널이 I/O를 적극적으로 관리하고, 완료 결과를 애플리케이션에 통지한다.”
Windows에서는 I/O 요청을 커널에 넘기는 순간, 실제 I/O 실행, 스레드 스케줄링, 완료 이벤트 관리까지 모두 커널이 책임진다. 애플리케이션은 I/O가 언제 가능해지는지를 감시하지 않고, 이미 완료된 작업의 결과만 처리한다.
이 철학은 IOCP(I/O Completion Port)를 중심으로 한 Proactor 기반 설계로 이어졌다.
어느 쪽이 더 좋은 설계일까?
Reactor와 Proactor 중 어느 한쪽이 절대적으로 우월하다고 보기는 어렵다. 두 모델의 차이는 단순한 구현 방식의 문제가 아니라, 각 운영체제가 선택해 온 설계 철학과 생태계의 방향성에 가깝다.
Linux는 “커널은 최소한만 개입하고, 제어는 사용자 공간에 맡긴다”는 철학 아래에서 발전해 왔다. 이로 인해 select, epoll로 대표되는 Reactor 중심 구조가 자연스럽게 자리 잡았고, 애플리케이션이 I/O 흐름을 직접 제어하는 방식이 일반적인 선택이 되었다. 반면 Windows는 “커널이 I/O를 적극적으로 관리하고 완료를 통지한다”는 방향을 택했고, 그 결과 IOCP 기반의 Proactor 모델이 정착되었다.
결국 중요한 것은 어느 쪽이 더 좋으냐가 아니라, 어떤 환경에서 어떤 모델이 더 잘 맞느냐라고 보는 편이 더 정확하다.
백엔드 개발자의 시선에서 본 Reactor와 Proactor
그렇다면 내가 공부하고 있는 백엔드 개발자의 관점에서는 어떨까. 백엔드 서버의 역할을 한 문장으로 정리하면, 요청을 받아 성공이든 실패든 반드시 하나의 결과로 귀결시키는 것이다. 백엔드는 요청을 처리하고 내부 로직을 거쳐, 결국 명확한 결과를 만들어 클라이언트에게 반환하는 책임을 진다.
이 관점에서 I/O 모델을 바라보면 자연스럽게 이런 질문이 떠오른다. 중요한 제어 흐름을 커널에 맡기는 것이 과연 괜찮을까?
Reactor가 백엔드 개발자에게 자연스럽게 느껴지는 이유
Reactor 모델에서 커널의 역할은 비교적 단순하다. 커널은 “이 소켓은 이제 읽거나 쓸 수 있다”는 상태 변화만을 통지하고, 그 이후의 흐름은 전부 애플리케이션의 몫이다.
언제 read()를 호출할지, 얼마나 읽고 멈출지, 에러가 발생했을 때 어떻게 처리할지에 대한 모든 결정권이 사용자 코드에 있다.
이 구조는 백엔드 개발자의 사고방식과 잘 맞는다. 요청 처리 흐름을 직접 통제할 수 있고, 비즈니스 로직과 I/O 처리 순서를 명확하게 설계할 수 있으며, 타임아웃·재시도·에러 처리 같은 정책도 코드 레벨에서 일관되게 관리할 수 있다. 즉, 결과를 책임지는 주체가 제어권도 함께 가져가는 구조다. 그래서 Reactor 모델은 많은 백엔드 개발자에게 “서버의 흐름을 내가 직접 쥐고 있다”는 느낌을 주기 쉽다.
그렇다면 Proactor는 백엔드에 불리할까?
반드시 그렇지는 않다. Proactor 모델에서는 I/O 실행 자체를 커널에 위임하고, 애플리케이션은 완료 이벤트를 받아 후처리에 집중한다. 이 방식은 대규모 I/O 처리, 스레드 관리 최적화, OS 차원의 고성능 I/O 스케줄링 측면에서 매우 강력한 장점을 가진다.
다만 백엔드 개발자의 시선에서는 “언제 읽혔는지”, “어떤 타이밍에 실행됐는지”, “중간에 어떤 상태 변화가 있었는지”를 세밀하게 제어하기 어렵게 느껴질 수 있다. 그래서 Proactor는 결과 처리는 명확하지만, 흐름 제어는 커널에 맡기는 모델이라고 볼 수 있다.
여기서 중요한 것은 어떤 패턴을 쓰느냐보다, 내가 책임져야 할 흐름을 어디까지 통제하고 싶은가다.
이번 글에서는 Reactor 패턴과 Proactor 패턴을 중심으로, I/O 멀티플렉싱과 비동기 I/O 모델이 운영체제마다 어떻게 다르게 설계되어 왔는지를 살펴보았다. select, epoll로 대표되는 Reactor 모델과, IOCP를 중심으로 한 Proactor 모델은 단순한 구현 차이가 아니라, I/O 실행의 책임을 어디에 둘 것인가라는 운영체제 철학의 차이에서 출발한다는 점도 함께 정리했다. 또한 백엔드 개발자의 관점에서 이 두 모델을 바라보며, 왜 Linux 환경에서는 Reactor 기반 구조가 자연스럽게 받아들여지는지도 고민해 보았다.
다음 글에서는 이러한 전통적인 구분을 조금 더 확장해 보려 한다. Linux에서 등장한 io_uring과 같은 최신 I/O 메커니즘은 Reactor와 Proactor의 경계를 흐리며 새로운 방향을 제시하고 있다. 또한 실제 백엔드 개발에서 사용하는 Spring, Netty 같은 프레임워크가 이러한 I/O 모델을 어떻게 추상화하고 활용하는지도 함께 살펴볼 예정이다. 이를 통해 이론적인 패턴 설명을 넘어, 실제 서버 애플리케이션에서는 I/O가 어떤 방식으로 다뤄지고 있는지까지 연결해 보고자 한다.
'CS > OS' 카테고리의 다른 글
| 비동기/ 동기가 뭐길래.. (2) | 2026.01.21 |
|---|---|
| I/O Multiplexing: Select, Poll, 그리고 Epoll (0) | 2026.01.19 |
| Block I/O vs Non-Block I/O 무슨 차이지..? (0) | 2026.01.19 |
