개발자들 사이에서 가장 많이 헷갈리는 것 중 하나가 동기와 비동기, 그리고 Blocking과 Non-Blocking 의 구분인 것 같다. 심지어 둘이 다르다는 것을 알긴 해도, 그 이유에 대해서는 또 잘 모르거나, 잘못 알고 있는 경우가 있다.
필자 또한 둘의 개념을 공부하면서 "엥? 그게 그거 아닌가?" 라고 생각했었지만, 자세히 뜯어보면 아예 다른 개념인 것을 알 수 있었다.
이번 글에서는 쉬운 예시와 함께 둘 사이의 분명한 차이점에 대해 다룰 것이다.
어떤 작업을 실행할 때, 요청한 작업이 완료되었는지 여부를 따지며 순차적으로 처리한다.
예시)
어떤 빵을 만들기 위해, “반죽을 오븐에 넣어 빵을 만들기” → “야채를 썰어 빵을 꾸밀 데코레이터 준비” → “오븐에 구워진 빵에 데코레이터 장식” 이라는 프로세스가 있다고 하자.
동기적으로 처리하면 다음과 같다.
즉, 오븐에서 5분 타이머를 걸어 빵이 완전히 구워질 때 기다리고, 완성될 때 다음 과정인 야채를 썰어 데코레이터를 준비한다.
어떤 작업을 실행할 때, 요청한 작업의 완료 여부를 신경쓰지 않고 처리한다.
위의 예시를 비동기적으로 처리해보자.
비동기 방식의 요리는 오븐에서 5분 타이머를 걸고 그 동안 동시에 야채를 썰어 미리 데코레이터를 준비한다. 즉, 빵이 오븐에서 구워지는 것과 무관하게 다른 작업을 한다.
그렇다면 Blocking, Non-Blocking의 차이는 뭘까?
: 어떤 작업을 실행할 때, “제어권”을 호출한 함수에게 넘겨주어, 그 함수가 완료될 때 까지 다른 작업을 막는다.
Blocking의 관점에서 다시 요리해보자.
억양의 차이가 있다. 즉, 오븐이 반죽에 구워질 동안, “아무것도 하지 않는다”는 것이 Blocking의 핵심이다. 요리는 온전히 오븐에 빵이 구워지는 것에 전적으로 결정되는 것이다.
: 어떤 작업을 실행할 때, 함수를 호출하되 “제어권”을 넘기지 않고, 다른 작업을 동시에 한다.
Non-Blocking 관점에서 다시 요리해보자.
이 역시, 오븐이 반죽에 구워질 동안, 오븐에 “신경을 쓰지 않는다”는 것이 Non-Blocking의 핵심이다. 즉, 요리의 단계는 이 오븐에 빵이 구워지는 것과 관계없이 계속 진행되는 것이다.
즉, 동기와 비동기, blocking과 non-blocking의 결정적인 차이는, 중심이 되는 내용이 “작업 순서”인지, 아니면 “제어권”인지, 그 차이에 있다.
따라서 이 개념을 정확하게 알고 있으면 sync-nonBlocking, async-blocking이 어떻게 이루어지는지 이루어질 수 있다.
즉, 일단 빵이 오븐에 넣어질 때 까지 야채를 써는 “후속 작업”을 하지는 않지만, 여전히 주기적으로 타이머를 확인하거나, 빵의 상태도 확인하는 등의 “다른 작업”을 실행할 수 있다.
실제 이 점이 사용되는 점은 게임 내의 로딩창이라고 할 수 있다.
게임이 준비되기 까지 계속 기다려도, 로딩창을 새로고침 하는 "또 다른 작업"을 수행하도록 하기 위해서는 Sync-NonBlocking을 사용한다.
: 이 방식은 흔히 개발자의 실수에서 발생해서 “안티 패턴”이라고 불리기도 한다. 왜 인지는 이 예시를 통해 확인할 수 있다.
이렇게 분명 비동기적으로 다른 작업을 할 수 있는데, 내 몸의 제어권이 오븐에게 넘어가서 다른 작업을 할 수 없게 된다는 것이다.
보통의 경우, 동기와 Blocking을 동일시 하고 비동기와 Non-Blocking을 동일시 하는 경향이 있다. 그러나, 둘이 함께 같이 쓰긴 해도 호출한 함수 (Callee)가 작업을 완료할 때 까지 대기를 하는지, Callee에게 제어권을 넘겨 본 함수의 제어권이 소실되는지, 이 여부에 따라 동기/비동기, Blocking/Non-Blocking을 구분하는 점이 가장 큰 차이라고 할 수 있겠다.