콜백 함수와 비동기 프로그래밍

콜백 함수(callback function)
콜백 함수란 쉽게 이야기하면 "함수에 파라미터로 들어가는 함수"이며, "순차적으로 실행하고 싶을 때 사용"한다. 말 그대로 나중에 호출하는 함수, 코드를 통해 명시적으로 호출되는 함수이다.
코드를 작성할 때, 하나의 함수를 각기 다른 요청 파라미터로 호출할 경우 다른 실행을 하고 싶다면 요청 파라미터에 따라 if-else문을 각기 작성할 수도 있을 것이다. 하지만 이 요청 파라미터의 종류가 수없이 많아진다면 어떨까? 각 파라미터에 해당하는 if-else문을 만드는 것은 매우 잘못된 프로그래밍이다.
코드에서 숫자 또는 문자를 직접 사용하여 프로그래밍을 하는 대신 int a, String b라는 변수를 이용하는 것처럼, 함수도 변수처럼 사용할 수 있다. 이렇게 하면 더 이상 서로 다른 요구사항에 맞춰서 코드를 매번 바꿀 필요가 없다. 함수 변수를 이용하여 이러한 문제를 해결할 수 있는 것이다.
콜백 함수 코드는 프로그래머가 구현하지만, 호출하는 것은 보통 다른 모듈이나 스레드이다.
우리는 어떤 일을 해야 하는지 알지만, 언제 하는지는 정확히 알 수 없다.
다른 모듈은 무엇을 해야 하는지는 모르지만, 언제 할지는 알고 있다.
따라서, 우리는 어떤 일을 해야 하는지의 정보를 콜백 함수에 잘 담아 다른 모듈에 전달해야 한다.
콜백 함수와 호출자는 서로 다른 계층에 존재한다.

서드 파티 라이브러리에 콜백 함수를 지정해야 하는 이유는, 서드 파티 라이브러리의 작성자는 특정 시기에 어떤 작업을 수행해야 하는지 모르기 때문이다. 서드 파티 라이브러리의 사용자가 콜백 함수를 구현하여 전달하면, 서드 파티 라이브러리의 작성자는 특정 시기에 해당 콜백 함수를 호출하기만 하면 된다.
콜백 함수가 호출되는 시점은?
콜백 함수는 일반적으로 이벤트가 발생했을 때, 이를 처리하는 코드 호출에 유용하다. 이 관점에서 콜백 함수는 이벤트 처리 도구(event handler)이며, 이벤트 중심 프로그래밍(event-driven programming)에 적합하다.
비동기 프로그래밍(asynchronization)
💥
함수A가 호출되고 나서, 해당 함수(A)가 콜백 함수를 처리하고 반환되는 실행 시간이 점점 길어질 수 있다.
반환에 오랜 시간이 걸리는 함수의 다음 코드가 굉장히 중요한 내용이라 이 긴 실행 시간을 기다릴 수 없는 상황이라고 가정해보자.
이 때 이 문제를 개선하려면 어떻게 해야 할까?
해당 함수(A) 내부에서 스레드 t를 생성하여 해당 스레드가 콜백 함수를 호출하도록 하면 해당 함수(A)는 새로운 스레드 t를 생성한 후 즉시 반환되어 다음 코드를 아무 지연 없이 바로 실행할 수 있을 것이다.
이런 방식이 비동기(asynchronization)이다. 비동기 방식으로 함수를 구현하면, 함수 A를 호출 후 오랜 시간 기다리지 않고 호출자와 피호출자가 각자의 스레드에서 병렬로 실행될 수 있다.
비동기 콜백(asynchronous callback): 호출 스레드가 콜백 함수 실행에 의존하지 않는 것, 지연 콜백(deferred callback)이라고도 한다.
기본적인 함수 호출 방식은 다음과 같고, 이것을 동기 호출(synchromous call)이라고 한다.
- 함수를 호출하고 결과 반환을 기다린다.
- 결과를 받는다.
- 받은 결과를 처리한다.
동기 콜백은 가장 익숙한 콜백 유형으로, 블로킹 콜백(blocking callback)이라고도 한다.
일반적인 동기 방식에서는 request 함수가 반환되는 것을 무조건 기다려야 하지만, 비동기 콜백 방식이라면 request 함수는 즉시 반환되며 실제로 결과를 받고 처리하는 프로세스는 다른 스레드, 프로세스, 심지어 다른 시스템에서 완료될 수 있다.
동기 콜백과 비동기 콜백의 흐름을 나타내보면 아래와 같다.

동기 콜백이 진행되는 동안에는 주 프로그램이 아무 일도 하지 못하지만, 비동기 콜백은 이러한 문제 없이 주 프로그램을 계속 실행할 수 있기 때문에 비동기 콜백은 동기 콜백에 비해 다중 코어 리소스를 더 잘 활용한다.
따라서 비동기 콜백은 입출력 작업, 동시성이 높은 시나리오에 적합하다.
그러나 비동기 콜백도 문제가 있다.
비동기 콜백의 단점을 이야기해보자면,
- 동기 콜백에 비해 이해하기가 어렵다.
- 비즈니스 구성이 복잡한 경우, 콜백 지옥(callback hell)에 빠질 가능성이 높다.
/* 콜백 지옥 */
// 코드
a = GetServiceA();
b = GetServiceB(a);
c = GetServiceC(b);
d = GetServiceD(c);
// 위 코드를 비동기 콜백 방식으로 작성하면?
GetServiceA(function(a) {
GetServiceB(a, function(b) {
GetServiceC(b, function(c) {
GetServiceD(c, function(d) {
...
});
});
});
});
▶ 비동기 콜백의 효율성 + 동기 콜백의 가독성 => 코루틴