실행 순서
, waiting
, 응답 여부
, 제어권
먼저 동기/비동기는 실행 순서와 관련된 개념입니다. 동기 방식은 특정 함수가 요청에 대한 응답을 기다린 후에 다음 작업을 처리하는 방식입니다. 반면, 비동기 방식은 응답 여부와 상관없이 다음 작업을 수행합니다.
그리고 블록킹/논블록킹은 제어권과 관련된 개념입니다. 블록킹 방식은 특정 함수가 다른 함수를 호출했을 때, 해당 함수에 제어권을 넘기고 작업이 완료되었을 때 제어권을 돌려받는 방식입니다. 반면, 논블록킹 방식은 제어권을 곧바로 회수하면서 호출된 함수의 작업 완료 여부와 상관없이 다음 로직을 실행합니다.
동기 프로그래밍은 직관적으로 코드를 이해하기 쉽고, 간단한 어플리케이션은 구현도 쉽고 안정성도 높아 효율적이다.
하지만, 성능상의 이점을 기대하기 어렵다. 오래걸리는 작업을 위해 CPU가 대기하는 경우가 발생하면 자원의 낭비가 발생한다. 그리고 여러 작업을 동시에 실행하도록 확장하기에 제한적이다.
비동기 프로그래밍은 CPU 자원의 낭비를 상대적으로 최소화할 수 있다. 다른 작업을 수행하면서, 비동기 작업이 수행되기 때문이다. 또, 동시에 여러개의 작업을 수행하게 되어야할 때, 작업이 추가되더라도 확장성이 높아 용이하다.
하지만, 구현이 복잡해질 수 있고, 결과를 핸들링하기 위한 코드도 필요하다. 그리고 결과를 제대로 핸들링하지 못했을 때 안정성이 떨어질 수 있다.
blocking I/O는 구현이 쉽고, 직관적으로 로직을 파악할 수 있다. 그리고 I/O 작업이 완료될 때까지 기다리기 때문에, 반환 결과가 보장이 되고 동기화 이슈를 해소할 수 있다.
하지만 I/O 작업이 오랜 시간을 필요로 하는 작업일 경우에는 성능 이슈가 발생할 수 있다. 그리고 I/O 작업이 수행되는 동안 다른 작업을 수행할 수 없어, 동시에 여러개의 I/O 작업을 수행하는 로직에는 적합하지 않다.
nonblocking I/O는 I/O 작업이 수행되는 동안, 다른 작업을 계속해서 수행할 수 있기 때문에 multiple I/O 작업을 동시에 handling하기에 적합하고 성능상의 이점이 존재한다. 결국 이 덕분에 확장성이 상대적으로 더 높다.
하지만 blocking I/O에 비해 코드가 복잡해지고, I/O 작업의 결과를 handling하는데 더 많은 코드량을 요구한다. 그리고 프로그램이 I/O 작업의 완료를 기다리지 않기 때문에, 결과값이 정확하지 않거나 동기화가 되지 않는 이슈가 발생할 수 있다. 그래서 이 결과값을 적절하게 컨트롤 할 수 있는 로직이 프로그램에 필요하다.
Polling
I/O 작업의 완료 여부를 주기적으로 프로그램이 체크하는 방식이다. I/O 작업이 완료될 때까지 이 작업은 계속된다.
Callback
I/O 작업이 완료되었을 때 호출될 함수로 callback 함수를 설정해둘 수 있다. 주로 여러 I/O 작업을 동시에 사용하는 경우에 적용된다.
Events
프로그램은 I/O 작업이 완료되었을 때 트리거가 작동하는 event를 기다린다. 이 방식은 일반적으로 polling 방식보다 효과적이다.
Promise 객체
Promise 객체를 사용해서, I/O 작업의 상태를 확인하고, 작업이 완료되면 Promise 객체를 업데이트 할 수 있도록 설계한다.
sync-blocking 과 async-nonblocking
사실 이 조합은 가장 자연스럽다.
먼저, sync-blocking을 보면, 동기 프로그래밍이니까 작업의 순서가 지켜져야 하므로, 요청한 결과가 돌아올 때까지 기다릴 것이다. 그리고 blocking이니까 호출한 함수에 제어권도 넘어간다. 가장 이해하기 쉽다. 결국 다른 작업이 수행되는 동안 아무것도 하지 않고 제어권도 넘겨놓고 기다리는 방식이다.
그리고 async-nonblocking도 자연스럽다. 비동기 프로그래밍이니까 작업의 순서는 고려대상이 아니다. 요청을 보내놓고 다른 작업을 수행할 것이다. 그리고 nonblocking이기 때문에, 제어권도 곧바로 돌려받는다. 다른 작업이 수행되는 동안 결과에 상관없이 제어권을 돌려받고 다른 작업을 수행하는 방식이다.
sync-nonblocking
이 조합은 조금 이상하다고 느낄 수 있다. 작업의 순서는 지켜져야 하는데, 함수 호출이 되고나서 제어권은 돌려받는다? 제어권을 돌려받았으니, 다른 작업을 수행할 수는 있지만, 결국 결과를 받아야 다음 순서의 작업으로 넘어갈 수 있다는 뜻인데, 이를 위해서 계속해서 작업이 완료되었는지 확인해야한다.
사실 nonblocking이라면, 제어권을 돌려줬으면 그걸 잘 써먹어야되는데, sync-nonblocking 조합에서는 돌려받는 제어권을 작업이 완료되었는지 확인하는데 쓴다. 좀 모순적이라고 할 수 있다. 그런데 이 모순적인 방식을 실제로 쓰는경우가 바로, Polling 방식과 timeout-based I/O 작업이다.
polling은 주기적으로 작업이 완료되었는지 확인하는 방식으로, sync 방식에서는 작업이 완료되었을 때 다음 작업으로 넘어갈 수 있겠다. 물론 polling은 async 방식에서도 사용이 가능하다.
timeout-based I/O 작업은 말 그대로, 특정 timeout 주기에 작업이 완료되어야 한다.
두 가지 케이스 모두, nonblocking 기반이어야 가능하다.
async-blocking
이 조합은 조금 문제가 있는 조합이다. 간단하게만 생각해봐도, 비동기 작업을 수행한다는 것은 순서에 신경 쓰지 않겠다는 것이다. 그런데 blocking을 걸면, 비동기 작업에 제어권을 빼앗겨서, 해당 작업이 완료되기 전까지는 다른 작업을 수행할 수 없다. 그럼 결국, sync-blocking 방식과 다를게 없다.
즉, 이 경우는 async-nonblocking 방식을 사용하려다가, 프로그래머의 실수로 async-blocking이 되었을 가능성이 매우 높다. 애초에 비동기 작업은 nonblocking 방식에서 실행되도록 설계되었기 때문이다.