본문 바로가기

개발

Blocking/Non-Blocking & Sync/Async

 

이 개념은 작년에 CS공부만 빡세게 하던 시기에 처음 봤던 개념인데, 그 당시 몇번을 읽어봐도 도당체가 이해가 되질 않았었다.. 내 머릿속엔 동기/비동기(Sync/Async)에 대한 개념만 있었어서이 친구들과  블로킹/논블로킹의 차이점이 정말 머리에 잘 들어오질 않았고, 수많은 글과 그림들을 계속 찾아보고서야 동기/비동기 하나로 뭉뚱그려 이해하고 있던 개념을 동기와 블로킹으로 분리하는 데에 겨우 성공했던 개념이다. 하지만 실무에서 직접 쓰는 일이 없다보니 시간이 지나 다시 점점 잊혀져 가다가.. 이번에 SpringWebflux 공부를 하다보니 flux에서 아주 핵심적으로 이해하고 있어야할 개념이길래 다시 한번 마주했다.

 

그런데 오랜만에 다시 찾아본 여러 글들에서도, 여전히 이해가 쉽도록 이 개념을 설명하는 사람들이 너무 없는 것 같아서  정말 이걸 초등학생도 이해할 수 있도록 설명할 수는 없을까하며(실패했다..) 이 글을 적어보고 있다.

 

그럼 익숙한 개념부터 하나씩 이해해보자.

 

Sync(동기) / Async(비동기)


논블로킹을 찾아보고 있을 사람이라면 아마 대부분 동기와 비동기 개념에 대해서는 어느정도 이해하고 있을 것이다.

이건 CS지식을 거의 패스하고 가르치는 국비학원들에서조차 이해를 시키고 넘어가는 개념이니말이다. 하지만, 논블로킹에 대한 이해가 없이 동기비동기에 대한 이해만 하고 있는 상태의 당신이라면, 아마 상당히 높은 확률로 두 개념을 혼용해서 이해하고 있을 것이다. 과거의 내가 그랬듯이..

 

그래서 내가 잘못 이해하고 있던 동기/비동기의 개념으로부터 시작해서 한번 설명을 해보겠다.

 

 

내가 이해하고 있던 Sync

-> a함수 내에서 b함수 호출 시, b함수의 동작이 끝나야지만 a함수가 이어서 실행된다는 개념

 

내가 이해하고 있던 Async

-> a함수 내에서 b함수 호출 시, b함수의 동작여부와 관계 없이 계속해서 a함수를 실행한다는 개념

 

결론부터 말하자면 이 설명은 정확히는 동기/비동기보다 블로킹/논블로킹에 가까운 설명이었다.

그렇다면 동기는 어떻게 설명해야할까?

 

진짜 Sync

-> a함수 내에서 b함수 호출 시, b함수의 수행 결과를 a함수가 기다린다는 개념

 

진짜 Async

-> a함수 내에서 b함수 호출 시, b함수의 수행 결과를 a함수가 기다리지 않는다는 개념

 

이렇게 들어도 그게 그거인 것 같을 것이다. 당연한 것이, b함수의 수행 결과를 a함수가 기다리려면 우리가 이해하고 있던 것처럼 일반적으로는 a함수가 하던 일을 멈추고 가만히 b를 기다리고 있는 것이 맞다. 하지만, b함수의 수행에 얼마나 많은 시간이 걸릴지도 모르는 건데 a함수는 그냥 하염없이 기다리기만 해야할까? 이렇게 기다리는 시간이 아까울 때 생각할 수 있는 방법이 비동기와 논블로킹이다. 여기서 비동기는 b함수를 기다리기를 아예 포기하는 것이고, 논블로킹은 기다리기 여부와 관계 없이 일단 다른 할 일을 하겠다는 것이다. 좀 더 쉽게 설명하기위해 내가 친구를 불러놓고 약속장소에서 친구를 기다리고 있는 상황을 가정해보자. 이럴때의 내행동에 따른 각각의 상황은 아래와 같다.

 

  • 동기/논블로킹 : 약속장소에서 기다리고는 있지만(동기) 동시에 폰으로 유튜브도 보고 있음(논블로킹).
  • 동기/블로킹 : 약속장소에서 기다리고 있는데(동기) 차렷자세로 가만히 서서 아무 행동도 아무 생각도 하지 않고 있음(블로킹)
  • 비동기/논블로킹 : 친구 기다리기를 포기하고(비동기) 그냥 내 할 일 함(논블로킹).
  • 비동기/블로킹 : 친구 기다리기를 포기했는데도(비동기) 제자리에서 동상마냥 아무 행동도 아무 생각도 안 하고 있음(블로킹).

어느정도 이해가 되는가? 이를 그림으로 나타내면 아래와 같다.

출처 : 인파데브님 블로그

 

이런식으로 기다리기 여부와 다른 동작 수행 여부에 따라 Sync/Blocking이 구분되는데, 그냥 동기/비동기만 하면 될 걸 왜 이렇게 복잡하게 블로킹이란 개념까지 만들어서 구분하고 있는건지 슬슬 이해가 안 되고 빡칠 것이다. 이해가 잘 안 된다면 대충 넘어가자. 나도 며칠 내내 이 개념을 공부하며 결론 내린 것이지만, 이 4가지 개념을 명확하게 구분하여 설명하는 것은 불가능하다. ChatGPT도 이 개념의 원론적인 설명에 대해서는 답해주지만, 개념을 혼합하고 예시를 보여줘가며 물어보면 애가 고장나서 이랬다저랬다 다른 답변을 내놓는다. 애초에 이렇게 개념을 구분해서 이해해볼 필요가 있는 이유는 다른 게 아니라 Sync&Non-Blocking방식 때문이다. 그렇기에 이 방식이 무엇이고 왜 필요한지만 잘 이해하면 된다고 생각하니 적당히 이해하고 넘어가자.

 

 

왜 Sync&Non-Blocking이 필요하게 되었나?


일반적으로는 특정 작업의 수행을 지시하고 다른 일을 하고 싶을 때 비동기식으로 처리하면 그만이지만, 비동기는 위에서 말했듯 호출한 함수의 결과를 기다리지않는다. 즉, 호출만 할 뿐 그게 어떻게 처리되었는지 호출부에서는 알 방법이 없는 것이다. 그렇기에 보통은 비동기 함수에서는 콜백함수를 통해 처리 결과를 핸들링하는데, 콜백함수는 어디까지나 호출부와는 별개의 영역에서 동작하는 함수이므로 호출부와의 데이터 공유가 어려울 수밖에 없다. 그리고 콜백이 많아질수록 함수가 무한히 생성되는 콜백지옥이라는 현상에 빠질수도 있기 때문에, 많은 비동기 처리를 동시다발적으로 수행해야하는 상황에 많은 콜백함수를 호출하는 것은 그다지 바람직하다고 할 수 없다. 또한 콜백으로 결과 처리가 분리되면 코드의 가독성 또한 떨어지며, 디버깅이나 테스트코드의 작성도 상당히 어렵게 된다는 문제점도 있다.

 

그렇기에 콜백함수 없이 그냥 비동기 처리 함수를 호출한 함수가 처리 결과에 대한 리턴 값을 받으면, 훨씬 깔끔하게 처리가 가능해질 것이다. 이런 방식을 가능하게 하기위해 동기/논블로킹이 등장하여, 호출 함수의 처리결과는 호출한 함수가 체크하지만 그 사이에 논블로킹으로 다른 작업을 할 수 있는 방법이 생긴 것이다. 이런 방식을 이용하는 대표적인 프레임워크가 이제 내가 공부중인 Spring WebFlux인데, 플럭스에 대한 자세한 설명은 다음에 웹플럭스 포스팅에서 다루도록 하겠다.

 

 

 

Blocking I/O & Non-Blocking I/O


refrence : https://techblog.woowahan.com/2667/

 

그리고 이 Sync/Non-Blocking이 특별히 더 유용한 상황이 있는데, 바로 대량의 I/O작업을 처리해야할 때이다. 여기서 I/O작업이란 Input/Output의 약자로써, PC주변장치와의 데이터 통신작업을 의미한다. 이렇게만 말하면 마치 특수한 경우를 말하는 것 같지만, 우리는 프로그램을 만들면서 이미 수많은 I/O 작업을 수행하고 있는데, 바로 API요청 작업과 파일 읽기/쓰기 작업 등이 그 대표적인 예이다. 왜냐하면 API요청은 결국 프로세스메모리 <-> 네트워크랜카드간의 I/O 작업이며, 파일 읽기/쓰기는 프로세스메모리 <-> 하드디스크간의 I/O이기 때문이다. 아무튼 이런 I/O작업의 동작 방식을 곰곰이 생각해보면 왜 I/O작업에 동기/논블로킹 방식이 유리한지 이해할 수 있다. 생각해보자, I/O작업이 아닌 프로세스 메모리 내에서만 연산이 일어나는 작업의 경우에는, 작업이 비동기로 처리된다고해도 결국 호출한 함수나 호출된 함수나 같은 메모리 내에서 작업이 처리된다. 그렇다는 것은, 만일 단일스레드 환경이라면 아무리 비동기 처리를 한다고 하더라도 결국은 하나의 스레드가 모든 작업을 순차적으로 처리하기 때문에 동시에 수행될 수 없고, 결국은 비동기함수의 연산이 종료될 때까지 호출부의 함수는 작동이 중단된 채 대기하게 된다. 반면에 외부와 통신하는 I/O작업의 경우를 한번 생각해보자. API요청을 하는 함수를 비동기로 호출하면, 해당 함수는 API서버로 HTTP요청을 보내기까지는 코어스레드가 연산을 진행하지만, 요청을 보낸 이후로는 따로 연산을 진행할 게 없다. 요청을 보내고 응답을 받기까지는 오로지 네트워크 통신 속도와 타겟서버의 처리 속도에 달려 있을 뿐, 프로세스는 발만 동동 구르고 기다려야하는 것이다. 그렇기 때문에 이런 I/O작업을 Non-Blocking방식으로 처리하면, 요청을 보내고 응답을 받기까지의 시간동안은 프로세스가 다른 작업을 진행할 수 있게된다. 그렇기에 동시에 여러 I/O처리를 수행해야하는 프로그램의 경우에는 Non-Blocking I/O방식을 통해 작업을 처리하는 게 성능상의 이점이 상당히 커지는 것이다. 그리고 여기에 Sync까지 적용하여 위의 비동기콜백의 단점까지 보완하면, 뛰어난 성능과 유지보수성을 가진 대량 I/O처리 시스템이 만들어지는 것이다.

 

 

 

 

 

 

# Reference

1. https://devmoony.tistory.com/174

2. https://inpa.tistory.com