백엔드 개발자들이 알아야할 동시성 2 — 블로킹과 논블로킹, 동기와 비동기
이번 포스팅에서는, 본격적인 이야기에 앞서 알아야할 두가지 개념에 대해서 더 알아보려고 합니다. 많은분들이 들어보셨을만한 블로킹과 논블로킹, 동기와 비동기의 개념에 대해서 이야기 해보겠습니다.
블로킹과 논블로킹
블로킹과 논블로킹은 내가 결과를 기다리는 대상을 어떻게 다룰것이냐에 대한 주제입니다.
블로킹 (Blocking)
블로킹은 요청에 대한 결과를 바로 줄 수 없는 경우 그 결과를 기다리도록 하는것을 의미합니다. 요청에 대한 결과를 바로 받을 수 없는 상황에서, 블로킹의 경우엔 결과가 나타날 때 까지 기다리게 됩니다.
우리가 집게리아에 전화했다고 예를 들어보겠습니다.
“여보세요, 징징이 있나요?”
“아뇨, 전 뚱이에요. 잠시만 기다리시면 징징이에게 연결 해드릴게요”
“네, 징징이 입니다”
이렇게 요청에 대한 응답을 받을때까지 기다리는것을 블로킹 이라고 합니다.
이렇게 블로킹은 요청에 대한 결과가 올 때 까지 쓰레드는 아무것도 하지 못하고 대기하게 됩니다.
논블로킹
하지만 논블로킹이라면 어떨까요?
“여보세요, 징징이 있나요?”
“아뇨 뚱인데요!” (뚝)
이렇게 논블로킹에선 결과를 기다리지 않고, 바로 종료됩니다. 요청에 대한 응답을 기다리지 않고, 다음 작업을 바로 수행 할 수 있게 되는것이죠.
위와 같이 논블로킹은 항상 요청에 대한 결과를 기다리지 않고 바로 반환을 하게됩니다.
이제 블로킹과 논블로킹, 동기와 비동기에 대해서 혼동하시는 분들은 이렇게 생각하실 수도 있습니다.
동기가 블로킹이고, 비동기가 논블로킹아니야?
하지만 이 둘은 매우 다른 의미를 지닙니다. 정확히는 서로 다른 영역에 대한 이야기를 하고 있는 용어이지요.
Synchronous / Asynchronous
Blocking / Non-Blocking이 현재의 작업 상태에 따라 동작이 결정되는것이라면, Synchronous / Asynchronous는 결과를 기다리는 주체가 누구인가에 대한 이야기입니다.
Synchronous
Synchronous는 결과를 기다리는 주체가 요청을 호출한 Thread입니다. 해당 Thread는 요청에 대한 결과가 돌아오기까지 아무것도 하지 않게 될것입니다. Blocking / Non-Blocking에서 나타낸 모든 예시는 사실 Synchronous인 경우를 가정하여 설명했습니다.
Synchronous이면서 Blocking인 경우에는, I/O 요청을 한 Thread가 I/O 응답이 올 때 까지 아무일도 안하고 기다리게 됩니다.
결과를 기다리는 주체가 호출한 자신이 되는것이지요.
System 함수 read()
, write()
가 이런식으로 동작하게 되어있습니다.
하지만 Synchronous이면서 Non-Blocking인 경우엔 I/O 응답이 아직 오지 않았다 자체가 결과가 되어 즉시 반환됩니다.
이렇게 Synchronous는 결과를 기다리는 주체가 호출한 Thread가 되는 상황을 이야기합니다. select()
, epoll()
등을 non-blocking 모드로 사용하면 이렇게 진행이 됩니다.
Asynchronous
하지만 Asynchronous는 요청한 주체과 결과를 기다리는 주체가 전혀 다를 수 있습니다. 다음 그림을 봅시다.
위 그림은 Asynchronous + Non-Blocking의 경우를 도식화 한 것 입니다. Asynchronous의 경우에는 위와같이 Callback을 등록함으로써 I/O 요청을 보내게 됩니다.
Callback을 등록한 결과는 기다림 없이 즉시 반환되게 되지요(Non-Blocking). 그리고 I/O 결과는 해당 Thread가 아니더라도 Signal을 통해 호출된 callback으로 처리되게 됩니다.
Asynchronous이면서 Blocking인 경우도 있지만 일반적으로 비효율적인 방식이라 잘 활용되지 않기에 굳이 소개하지는 않도록 하겠습니다.
이렇게 기본적인 개념소개를 마무리하고 다음 포스팅에선 가장 대중적인 방법인 Synchronous / Blocking 방식이 왜 비효율적인 네트워킹의 대명사로 스포트라이트를 받고 있는지 소개해보도록 하겠습니다.