성장, 그리고 노력

부족하더라도 어제보다 더 잘해지자. 노력은 절대 배신하지 않는다.

Javascript

동기(Synchronous) vs 비동기(asynchronous) 그리고 IOC

제이콥(JACOB) 2020. 2. 1. 15:34

 사실 이 주제는 자바스크립트에만 종속된 이야기는 아니지만, 대부분의 개념을 자바스크립트를 통해 설명할 것이기 때문에 자바스크립트라는 카테고리에 위치시켰다. 

 

동기(Synchronous) vs 비동기(asynchronous)

동기와 비동기로 작성한 코드의 가장 큰 차이 중 하나는 런타임(runtime)시에 발생하는 지연시간(latency)이다. 

 

 모든 코드가 동기적으로(순서대로만) 실행된다면 어떤일이 벌어질까? 내가 만약 메시지를 보낸다고 하면 메시지를 보내고 응답이 오기까지 아무것도 할 수가 없다. 왜냐하면 앞에 작업이 아직 안 끝났기 때문이다. 물론 이런 방식이 전혀 사용이 안되는 것은 아니다. ATM 기계, 키오스크, 단순 단말기 같은 방식은 이런 동기적인 방식을 따르고 있다. 

 

동기적 방식의 장점은 코드를 파악하기 쉽고 유지보수, 디버깅이 훨씬 쉽다. 생각해 보면 코드가 차례대로 순서대로 실행되기 때문에 breakpoint를 한 단계씩 늘려가면서 디버깅 한다면 에러를 발견하는게 매우 용이할 것이다.

 

 하지만 이런 동기적인 방식은 싱글 스레드를 사용하는 자바스크립트에서는 문제가 되는 경우가 많다. 예를 들어 데이터베이스 작업이 완료되어 데이터가 오기까지 기다린다면, 우리의 애플리케이션은 유휴(Idle) 상태가 되고, 다른 작업을 할 수 있는 시간이 낭비된다.

이렇게 동작하도록 작성되는 코드를 "블록킹(blocking) 코드"라고 한다.

 이런 방식은 위에서 예로든 경우에는 적합하나, 웹에서 UI를 이런식으로 구현하면 안된다. 브라우저가 응답하지 않는다라는 메시지를 제공하는 최악의 UX를 제공할 수 있기 때문이다. 아래는 간단한 블록킹 코드 예제다.

const data = blockingHttpClient('/data');
// ...

 서버로부터 데이터를 요청하는 것외에도 클릭, 입력 이벤트 등을 모두 동기적으로 처리한다면 대기시간에는 차이가 있겠지만 결국 우리의 애플리케이션은 멈출 것이다. 

 

논블록킹 코드와 콜백 함수

 자바스크립트에서는 저런 문제를 해결하기 위해 콜백 함수를 사용해 왔다(다른 대안들이 많지만, 일단!). 콜백 함수를 사용하여 데이터가 준비가 되었을 때, 자바스크립트 런타임에 호출할 (콜백) 함수를 제공해줌으로써 블록킹 문제를 해결하는 것이다. 그리고 이 과정에서 제어의 역전이라는 개념이 등장한다. 제어의 역전(IOC, inversion of control)이란, 코드의 특정 부분이 런타임 시스템에서 제어권을 되돌려 받는 방식을 말하며, 한글로는 '제어의 반전'이라고도 불린다. 일단 이 말을 이해 못해도 좋다. 아래 간단한 채팅 프로그램 예제를 보고 이해를 해보자.

https://www.codeproject.com/Articles/592372/Dependency-Injection-DI-vs-Inversion-of-Control-IO

위 프로그램의 이벤트에 따른 흐름을 분석해 보면 아래와 같다. 

1. 사용자가 채팅 메시지를 보낸다.
2. 사용자는 자신의 작업을 계속 진행한다. -> "데이터를 기다리지 않고 다른 작업을 함"
3. 응용 프로그램은 이벤트를 수신한다. -> "데이터가 준비 되었을 때"
4. 메시지가 도착하면 이벤트가 활성화되고 메시지가 수신되어 표시된다. -> "콜백 함수 호출, 제어권의 역전"

 자세히 보면 이벤트의 흐름이 순차적이지 않음을 알 수 있다. 콜백 함수는 중간에 제어권을 반전시키고 있으며, 순서에 따라 이벤트 흐름을 제어하는 것이 아니라 이벤트가 프로그램 순서를 주도하는 것을 알 수 있다. 이런 이벤트 흐름 접근 방식은 직접 호출을 하지 않으므로(콜백을 사용하여 제어권을 역전), 프로그램의 유연성은 높아지고 비동기적 호출을 할 수 있다. 애플리케이션이 나중에 제어권을 반환하면서 그도안 프로그램은 다른 작업을 계속할 수 있는 것이다. 

 

 특히 일반적으로 다른 명령의 실행보다 느린 I/O 작업에 중요한 방식이다.프로세스 주기에 의존하지 않고 백그라운드에서 프로세스를 실행할 수 있기 때문이다.


비동기적 실행은 항상 좋은가?

 비동기 함수를 구현하면 지속적으로 응답할 수 있게 해주지만, 동기적 방식에 비해 많은 비용이 발생한다. 동기적 실행에 대해 다시 한번 봐보자. 

// 동기적 실행(프로그램 실행 방향 ->)
1단계 -> 2단계 -> 3단계

 동기적으로 코드를 작성하면 각 단계가 순서대로 실행되는 구문들의 절차라고 표현할 수 있다. 그리고 각 단계의 결과는 이전 단계의 결과에 의존하게 된다. 변수 및 다음에 발생할 상황을 쉽게 파악할 수 있으며, 코드 작성 및 디버깅이 쉽다. 물론 지연 시간과 완료 시간이 각각 다르다면 모든 작업이 함께 작동한다고 보장할 수 없다. 

 

 반면 비동기적으로 코드를 작성하면 위에서 1단계, 2단계, 3단계 작업이 독립적인 작업이라면 어떤 순서로 실행되더라도 상관이 없으며, 동시에 종료될 필요도 없다. 하지만 그렇기 때문에 1단계에서 발생한 데이터를 2단계에서 사용할 수 있다고 보장할 수 없다.

// 비동기적 실행(프로그램 실행 방향은 아래로!)
1단계 --------- 완료
 |
2단계 ---------------------- 완료
 |
3단계 ---완료

 그래서 사람들은 각 작업을 순서대로 실행되게 보장되도록 하기 위해 노력했으며, 이런 비동기적 함수 구조를 하나로 묶는 체인을 만들기 시작했다. 즉 콜백 함수를 중첩시키는 것이다. 이렇게 하면 비동기 호출이 일어나더라도 각 단계의 순서를 보장할 수 있었다.

 

 하지만 사용자의 요구 사항이 점점 늘어나고, 비동기적 구현이 늘어날 수록 콜백의 깊이는 깊어갔다. 그러다 보니 콜백이 점점 중첩되어 콜백 지옥(callback hell)에 빠지는 상황이 발생했다. 그래서 ES6 이후부터는 여기에 대한 해결 방법을 여러가지 제시한다. 아래 다른 글을 참고하자.

See Also

2020/01/27 - [Javascript] - 1부 - 자바스크립트 함수 표현식, IIFE 그리고 비동기

2020/01/28 - [Javascript] - 2부 - 자바스크립트 비동기적 프로그래밍(콜백, 프라미스)

반응형