봉쇄/비봉쇄(Blocking/Non-Blocking), 동기/비동기(Sync/Async), 그리고 이벤트 드리븐 아키텍처
통신(파일 I/O, 네트워크I/O) 개념에서 비동기와 논블로킹 개념은 매우 혼동하기 쉬운 주제이다.
네트워크를 정확히 모른다는 것은 장님이 코끼리 만지는 격이니, 이번기회에 제대로 정리하고 넘어가보자.
전제
- 이 방식은 모두, 이 패턴을 따를 때 적용된다.
- 호출자가 어떤 작업을 요청하고,
- 처리자가 그 결과를 돌려준다.
- 코드의 흐름이 동기/비동기인것은 문제에서 제외하고, 통신 개념에서의 동기/비동기에 집중해주길 바란다.
- 코드의 흐름이 동기/비동기인 것과, 외부 서버와의 통신이 동기/비동기인 것은 문제가 발생하는 상황이 다르다.
- 이 과정에서, 호출자의 대응 전략에 집중해보자.
참고) 좌측 코드의 흐름은, 블로킹/논블로킹이 결정되지 않은 상태이다. get() 시 데이터가 준비되어 있다면 논블로킹처럼 동작할 것이고, 준비되지 않았다면 블로킹처럼 동작할 것이다. 자세한 규칙은 아래 내용을 진행하면서 이해해보자.
동기/비동기(Synchronous/Asynchronous)의 정의
먼저, 정보통신기술용어사전(TTA)의 동기/비동기 정의를 가볍게 알아보자.
TTA의 정의
- 동기(Synchronization)
- 주기적인 운동을 하는 개체들이 서로 영향을 주고받거나 받게 됨으로써, 동일한 주기를 갖게되는 것, 그러한 현상을 동기 현상(同期現狀)이라 하고, 동기된 상태를 동기화(同期化) 되었다고 한다, 통신에서는 주로 서로 다른 시스템이나 네트워크에서 클럭 주파수나 비트, 프레임, 워드 등을 일치시키는데 사용된다.
- 비동기(Asynchronous)
- 연관된 메시지가 연대순 및 절차적으로 분리되는 경우 비동기적인 상호 작용이라고 한다. 요청-응답의 상호 작용을 예로 들면, 클라이언트 에이전트는 미래의 불확실한 시점에서 응답을 처리할 수 있다. 이렇게 하기 위한 메커니즘에는 폴링, 다른 메시지 수신의 알림 등이 있다.
현재 이 예시는 다른 주제들과 섞여있어 크게 와닿지 않는다. 좀 더 구체적으로, 요청과 응답 간의 시간적 결합 여부를 기준으로 생각해보자.
동기(Synchronous)란?
- 요청과 응답이 시간적으로 연속으로 발생하는 것을 의미한다.
- 요청자와 응답자의 각각의 상태가 동일한 주기, 즉 "동기화된 상태"를 유지하는 통신 방식을 의미한다.
- 사람으로 비유하자면, 서로가 서로에게 집중하고 있는 상태를 생각하자.
비동기(Asynchronous)란?
요청자와 응답자 간의 통신이 연속적으로 발생하지 않고, 응답자의 응답 준비 완료가 첫 응답과 분리된 방식으로 응답하는 통신 방식이다.
최대한 발생할 수 있는 경우의 수를 다 커버하려 하다보니 모호한 문장이 탄생했는데, 대표적인 예시를 들도록 하겠다.
비동기 통신 방식은 다음과 같이, Pull/Push 두가지 방식으로 나눌 수 있다.
- 풀(Pull)
- 요청자 쪽에서 응답이 준비 완료되었는지 주기적으로 확인하는 것.
- 폴링/롱 폴링
- 푸시(Push)
- 응답자 쪽에서 사전에 지정해둔 포트로 응답을 직접 보내는 것
- 이벤트/인터럽트 전달
이 두 방식은 모두 요청자의 요청과 응답자의 응답 사이에 다른 작업을 수행할 수 있는 상태로, 요청자와 응답자가 동일한 주기를 갖지 않는 상태를 만들어낸다.
참고 - 시퀀스 다이어그램에서 비동기 메시지는, 원래 저렇게 채워져 있는 화살표가 아닌, 비워져 있는 화살표로 표현된다.
mermaid의 한계로 인한 결과이니, 양해 바란다.
봉쇄/비봉쇄(Blocking/Non-Blocking)의 정의
이젠 블로킹/논블로킹에 대해 알아보자.
그 전에, 응답 데이터의 완성을 분리하고 생각해보자.
그렇다면, 순수하게 스레드(요청자)가 호출 시 응답자를 기다리느냐 / 호출 즉시 반환되는가의 차이만 있다.
다른 방식으로 표현하면, 제어권 반환의 시점을 기준으로 잡는다고 생각하면 좋다.
블로킹
- 만약 호출자가 응답자에게 특정 리소스를 요청했을 때, 리소스를 받을 수 있을 때까지 "아무 작업도 하지 않고" 대기하는 것을 의미한다.
- busy-wait과 같은 무의미한 반복도 블로킹으로 본다.
논블로킹
- 만약 호출자가 응답자에게 특정 리소스를 요청했을 때, 리소스를 받을 수 있는 여부에 상관없이 즉시 돌아오는 것.
- 유의미한 작업을 하지 않는(정지 상태인) 시간이 항상 0이라면, 해당 API는 Non-Blocking이다.
- 단순히 "호출 시점에 블로킹 없이 즉시 반환된다"는 의미이다.
- 제대로 된 데이터가 될 때까지 다른 작업을 하면서 기다렸다가 데이터를 완성해서 쓸지, 아니면 해당 값의 상태를 신경쓰지 않고 즉시 처리를 할지는 동기/비동기의 영역이다.
예시: 논블로킹 I/O와 Asynchronous I/O의 차이
- Operating System Concepts - 12.3.4절에 상세한 내용이 있으니, 참고 바란다.
- Non-Blocking I/O
- 사용 가능한 데이터만 있는대로 즉시 반환을 요청한다.
- 요청한 전체 바이트
- 일부 바이트
- 아예 없는 경우
- 사용 가능한 데이터만 있는대로 즉시 반환을 요청한다.
- Asynchronous I/O
- 요청된 전송이 미래 시점에 완료될 것이 보장되있는 경우 사용하는 방법이다.
- 요청된 전송이 완료되었을 때, 인터럽트로 완료됨을 통보받는다.
발생 가능한 경우의 수
그래서, 블로킹/논블로킹과 동기/비동기로 발생할 수 있는 경우의 수는 어떤 것들이 있을까?
지금부터 하나씩 알아보자.
동기 + 블로킹

- 클라이언트는 서버에게 동기 요청을 보낸다. 따라서, 서버와 클라이언트 간에는 같은 작업을 한다는 “동기화”가 보장되어 있다.
- 서버는 작업을 모두 수행한 뒤, 클라이언트에게 응답을 전송한다. 이때, 클라이언트는 아무것도 수행하지 않으면서 서버의 작업을 기다려주는 행동을 수행하였으므로, 블로킹되었다.
동기 + 논블로킹

- 클라이언트는 서버에게 동기 요청을 보낸다. 따라서, 서버와 클라이언트 간에는 같은 작업을 한다는 “동기화”가 보장되어 있다.
- 서버는 현재 응답할 수 있는만큼 클라이언트에게 즉시 응답을 보낸다. 이때, 클라이언트는 아무것도 수행하지 않으면서 서버의 작업을 기다려주는 행동을 수행하지 않았으므로, 클라이언트는 블로킹되지 않았다.
비동기(풀 방식) + 논블로킹

- 클라이언트는 서버에게 작업 요청을 보낸다. 이때, 서버는 클라이언트에게 클라이언트가 요청한 작업의 ID를 부여하여 리턴한다.
- 클라이언트는 서버가 작업을 완성하는 동안, 다른 작업을 수행한다. 이때, 클라이언트는 서버가 작업하는 동안, 다른 작업을 하고 있으므로, 클라이언트와 서버 간에는 동기화되지 않았다.(비동기) 또한, 클라이언트는 아무것도 수행하지 않으면서 서버의 작업을 기다려주는 행동을 수행하지 않았으므로, 클라이언트는 블로킹되지 않았다.
비동기(푸쉬 방식) + 논블로킹

- 클라이언트는 서버에게 작업 요청을 보낸다. 이때, 서버는 클라이언트에게 구독 정보를 리턴한다.
- 클라이언트는 서버가 작업을 완성하는 동안, 다른 작업을 수행한다. 이때, 클라이언트는 서버가 작업하는 동안, 다른 작업을 하고 있으므로, 클라이언트와 서버 간에는 동기화되지 않았다.(비동기) 또한, 클라이언트는 아무것도 수행하지 않으면서 서버의 작업을 기다려주는 행동을 수행하지 않았으므로, 클라이언트는 블로킹되지 않았다.
이벤트 드리븐 아키텍처의 정의
그래서 이벤트 드리븐 아키텍처란 무엇인가?
- 이벤트의 발행의 집중
- 엔티티의 상태 변화를 중심으로 아키텍처를 설계하는 패턴을 의미한다.
- 상태 기계에 대한 이해를 바탕으로 이해하면 좋다.
- 단순히 텍스트 메시지를 전달하는, 메시지 기반 아키텍처와 혼동하지 말자.
따라서, 이벤트 드리븐 아키텍처는 위에서 정의한 Asynchronous + Non-Blocking 방식으로 동작하게 된다.
- 같이 알면 좋은 개념
- 관련 주제
참고 자료
- Operating System Concepts, 10th Edition (원문을 보기를 권장한다. 번역판의 경우, 오역이 매우 많다.)