동기, 비동기, 블로킹, 논블로킹

Ziggy Stardust·2025년 9월 11일
0

개인적인 생각을 정리해봤습니다. 다른 분들과 의견이 다를 수 있고 틀린 부분이 있을 수 있으니 그런 부분은 알려주신다면 정말 감사하겠습니다.


프레임워크나 프로그래밍 언어의 컨셉을 깊게 공부한다면 당연히 마주하는 개념 동기, 비동기, 블로킹, 논블로킹.

왜 이해하기 어려울까

잘 정리된 글을 읽어봐도 남에게 명쾌하게 설명하기는 힘들었다. 나자신부터 제대로 이해를 못하였기 때문인데 왜 이렇게 이해하기가 힘들었을까.

원인은 이러한 개념이 어떠한 하나의 행위에서 발생할 수 있는 '현상' 임을 분명히하지 않아 그런것이라 생각한다.

그 행위를 프로그래밍 언어 느낌나도록 설명을 하자면 함수A에서 함수B를 호출했을 때 어떠한 동작을 하는가이다.

여기서 어떠한 동작은 프레임워크, 프로그래밍 언어에 따라서 다르고 같은 프레임워크, 프로그래밍 언어에서도 다양하다.

내가 말하고 싶은건 어떠한 원인으로부터 나오게 될 결과, 즉 현상부터 집중할 것이 아니라 그 원인, 행위에서부터 찾아가자는 것이다.


행위로부터 현상을 분석해보자

[동기, 블로킹이 발생하는 행위]
함수A가 함수B를 호출한다. 이 과정에서 함수A는 단순하게 함수B가 무사히 완료하고 결과값을 반환할때까지 기다릴 수 있다. 평범한 사람의 사고로 생각했을 때 이 과정은 정말 자연스럽다고 생각한다. 이러한 과정에서 발생한 '현상'을 정리해보자. 우선 처음 제어권은 함수A가 가지고 있다. 그렇기에 제어권을 가지고 함수B를 호출할 수 있다. 그 뒤에는 함수B를 호출하며 함수B에게 제어권을 넘기고 함수B의 로직을 수행하도록 한다. 제어권을 넘겼고 함수A는 함수B의 결과를 얻기 위해 대기를 한다. 이 과정에서 함수B의 결과를 얻기위해 함수A가 함수B에게 제어권을 넘기며 호출 이후의 로직을 수행하지 않은건 블로킹 현상으로 볼 수 있다. 그리고 함수A는 함수B가 결과를 줄 때까지 함수B의 결과완료상태에 신경써야한다.(함수B가 완료되어야 응답을 받고 함수A의 다음 로직을 수행할 수 있기에) 이건 동기 현상으로 볼 수 있다. 동기와 블로킹 현상은 단순하게 작업을 처리하려할 때 발생할 수 있는 현상이다.

[비동기, 논블로킹이 발생하는 행위]
앞서 강조한듯 이러한 현상은 단순하게 작업을 처리하면서 발생하는 일이다. 소프트웨어는 생명과도 유사하게 성장한다. 단순하게는 서비스의 품질을 만족하지 못할 수 있다. 그래서 조금 더 효율적으로 작업을 처리해야한다.

함수A가 함수B를 호출한다. 함수A는 함수B를 호출했지만 함수B의 결과를 기다리지 않고 제어권을 함수B에 줬다가 바로 돌려받는다. 그리고 함수A 자신의 작업을 이어간다. 이제 기다리지 않고 자신의 작업을 바로 이어갈 수 있으니 더 효율적으로 작업을 수행할 수 있게 된 것이다. 그리고 함수B가 완료되었을 때 후속작업을 처리해준다. 이 행위에서 함수A가 함수B 호출 이후 제어권을 바로 돌려받고 함수A 자신의 작업을 바로 수행하는 것은 논블로킹 현상으로 볼 수 있다. 그리고 함수A가 함수B 호출 후 함수B의 완료여부에 무신경한 것은 비동기 현상으로 볼 수 있다.

[그 외]
대부분의 프레임워크나 프로그래밍 언어에서는 위 두 가지 패턴이 많이 사용된다. 정말 단순하게 해결하던가 아니면 최대한 효율적으로 하던가 둘을 선택한다. 하지만 (동기, 논블로킹) , (비동기, 블로킹) 이러한 현상이 나타나는 행위들 또한 존재한다.
이런 두 방식에는 현실적인 괴리와 비효율성이 존재해서 보기가 힘들다.

[동기, 논블로킹이 발생하는 행위]
동기, 논블로킹 현상이 일어나는 대표적인 예시로는 '폴링' 방식이 있다. 주기적으로 요청을 보내는 방식이다. 구체적인 예로는 실시간과 유사하게 기능을 구현하려고 짧은 주기로 서버에 요청을 보내는 그런 방식을 의미한다. 폴링은 자신의 원래 목적 로직은 수행하면서 주기적으로 자신이 원하는 서버의 상태를 주기적으로 신경을 쓴다. 폴링 방식은 이러한 주기적인 요청으로 인해 비효율적이다는 문제를 겪는다. 이러한 행위에서 주기적으로 서버의 상태를 신경 쓰는게 동기 현상으로 볼 수 있다. 위에서 동기, 블로킹 방식의 예에선 함수B의 완료여부를 주기적으로 신경 썼던 것과 같은 맥락이다.

[비동기, 블로킹이 발생하는 행위]
비동기, 블로킹 현상이 일어나는 행위는 정말 비효율적인 부분이 크다. 그렇기에 적절한 예가 떠오르지 않는다. 그저 현실묘사를 통해서 어떤 부분이 비효율적인가를 생각해보자.

카페에 가서 '아이스 아메리카노'를 주문시킨다고 하자.
프론트에서 주문을 한다. 이는 함수B를 호출했다고 봐도 다르지 않다. 이제 '아이스 아메리카노'가 완성되면 알아서 호출해줄 것이기에 신경쓰지 않고 돌아와 알고리즘 문제를 하나 더 풀어도 괜찮다. 하지만 어찌된 영문인지 프론트 앞에 어두커니 서서 그 자리를 버티는 것이다. 지나가던 사람이 주문자를 쳐다보고 이 사람이하고 있는 생산적인 행위가 무엇이냐라는 질문에 대답을 한다면 여지없이 '숨쉬기' 혹은 '원활한 혈액순환' 정도로 말을 꺼낼 것이다.
기껏 음료를 주문하고 자유로운 시간이 생겼음에도 아무런 행동 없이 기다리는 것은 비효율적, 낭비라고 말할 수 있다.

일부러 이러한 동작을하는 경우는 크게 없기에 적절한 예시를 찾지 못하였다. 어쩌면 컴퓨터라는 기기의 상태 회복과 부담을 줄이기 위해 이러한 동작을 유도할 수도 있겠지만 더 나은 방식이 있을 것 같다.

지금까지는 단순히 행위로부터 현상을 분석한다는 느낌으로 동기, 비동기, 블로킹, 논블로킹을 정리해보았다. 즉 개념만을 설명하였고 조금 더 기술적인 내용도 섞어내는 것이 와닿을 것이다.


행위는 어떻게 구현되는가

이러한 행위를 프레임워크, 프로그래밍 언어에서 구현해내기 위해 어떠한 기술들을 선택했을까.

이번에는 일반적인 (동기, 블로킹), (비동기, 논블로킹) 에 대해서만 알아보자.

자바, 자바스크립트 같은 프로그래밍 언어에서는 논블로킹은 다른 스레드에게 작업을 위임함으로 제어권을 되찾는다. 함수B가 제어권을 바로 빼앗긴다해도 함수B 또한 수행이 되어야 한다. 즉 함수B 독자적인 제어권을 가진다는 것이다. 이를 스레드를 통해서 이뤄낸다. 자바의 멀티스레드, 자바스크립트 노드에서의 워커스레드가 예시이다.


비동기, 논블로킹은 무엇을 최적화하나

비동기, 논블로킹을 유도함으로 만들어내는 최적화는 선후 작업이 독립된 경우에 가능하다.

모든 상황을 효율적으로 처리할 순 없다. 세상에는 효율적인 처리를 위해 나눌 수 있는 일이 있고 나눌 수 없는 일이 있다. 원자를 나누는게 상당히 어려운 일인듯 효율적으로 처리하고 싶어 작업을 나누고 싶어도 불가능한 일이 존재한다는 것이다. 그건 선후행 작업간에 강한 결합, 의존이 존재할 때이다. 이 경우엔 비동기, 논블로킹이 이뤄지도록 나눠 실행할 수가 없다.

한 가지 예시로는 사진을 다운받아 크기를 조정하는 작업이 있다하자.

// 1. 사진 다운로드 후 저장
// 2. 사진 크기 조정

비동기, 논블로킹이 효율적이기에 어느 상황에서나 만능기처럼 동작할거 같지만 2번 작업은 1번이 완료하기 전엔 논리적으로 실행이 불가능하다. 블로킹이 반드시 일어날 수 밖에 없는 것이다.
그래서 비동기, 논블로킹이 효율화, 최적화를 시키는 것은 선후행 작업이 독립적인 경우일 때다.

얕은 예시로 아래와 같다.

// 1. API 접근 정보 로깅
// 2. 결과값 계산

1번 로깅이 끝나는 것과 2번 작업이 시작하는 것 사이에는 큰 연관이 없다. (요구사항에 따라 다르지만 1번 작업의 실패가 전체 작업의 실패로 처리해야할 정도로 중요한 것이 아니라면) 이런 경우 1번 작업은 비동기, 논블로킹 현상이 일어나게끔 다른 스레드에 위임해서 실패하던 성공하던 신경쓰지 말고 2번 작업을 바로 시작하는 것이다.


주절주절

행위나 현상으로 설명을 하는데 용어가 적절한지 모르겠다. 더 적절한 것이 있다면 좋겠다.

profile
spider from mars

0개의 댓글