두 축은 서로 다른 것을 묻습니다. 블로킹과 논블로킹은 제어권을 바로 돌려주는지, 동기와 비동기는 완료 통보를 누가 챙기는지입니다. 이 둘을 하나로 묶어 외우면 네 조합 중 두 개를 설명할 수 없게 됩니다.
두 축
| 축 | 묻는 것 |
|---|---|
| 블로킹 / 논블로킹 | 호출한 함수가 제어권을 바로 돌려주는가 |
| 동기 / 비동기 | 작업이 끝났는지를 호출한 쪽이 확인하는가, 통보를 받는가 |
네 조합
| 조합 | 동작 | 예 |
|---|---|---|
| 블로킹 + 동기 | 끝날 때까지 멈춰 서서 기다린다 | 일반 파일 읽기 |
| 논블로킹 + 동기 | 제어권은 바로 받고, 됐냐고 계속 물어본다 | 반복 폴링 |
| 블로킹 + 비동기 | 멈춰 서지만 여러 작업을 한 번에 지켜본다 | 다중 입출력 감시 |
| 논블로킹 + 비동기 | 맡기고 진행하다 끝나면 통보를 받는다 | 콜백, 이벤트 알림 |
논블로킹인데 동기인 조합이 자주 빠집니다. 제어권을 바로 돌려받아도 완료 여부를 내가 계속 확인해야 하면 동기입니다. 제어권과 완료 통보는 별개입니다.
왜 백엔드에서 중요한가
요청 하나마다 스레드를 붙이고 블로킹으로 기다리면, 느린 외부 호출 하나가 스레드를 붙잡습니다. 스레드가 바닥나면 CPU 가 한가한데도 새 요청을 못 받습니다.
논블로킹과 비동기로 바꾸면 기다리는 동안 그 스레드가 다른 요청을 처리합니다. 적은 스레드로 많은 연결을 감당하는 구조가 여기서 나옵니다.
어디를 안 막는지가 다르다
두 축이 헷갈리는 이유는 막지 않는 대상이 다르기 때문입니다.
| 축 | 무엇에 대한 이야기인가 |
|---|---|
| 동기와 비동기 | 결과를 누가 언제 알려 주나 |
| 블로킹과 논블로킹 | 부른 함수가 곧바로 돌아오나 |
그래서 둘은 독립입니다. 곧바로 돌아왔지만 결과를 직접 확인해야 하는 조합이 있고, 돌아오지 않지만 끝나면 알려 주는 조합도 있습니다. 이 네 칸을 구분하지 못하면 성능 이야기가 엉킵니다.
실제로 무엇이 걸리나
서버에서 중요한 것은 이름이 아니라 기다리는 동안 그 흐름이 다른 일을 하는가입니다.
막고 기다리면 그 스레드는 아무것도 못 한다
그래서 동시 요청 수가 스레드 수에 묶인다
막지 않으면 적은 스레드로 많은 기다림을 다룬다
셋째가 비동기 방식을 쓰는 이유입니다. 다만 그 안에 막는 호출 하나가 섞이면 이점이 통째로 사라집니다. 한 자리에서 막으면 그 흐름이 담당하던 모든 요청이 함께 멈춥니다.
흔한 오해
비동기가 항상 빠른 것은 아닙니다. 작업 하나만 놓고 보면 걸리는 시간은 같습니다. 이득은 기다리는 동안 다른 일을 한다는 데서 오므로, 기다림이 없는 CPU 연산에는 효과가 없고 코드만 복잡해집니다.