# 블록킹, 넌블록킹, 동기, 비동기 블록킹(Block), 넌블록킹(Non-Block), 동기(Synchronous), 비동기(Asynchronous)는 I/O 작업, 특히 네트워크 통신에서 자주 마주치는 개념이다. 애플리케이션의 효율성과 성능을 결정하는 중요한 요소다. 이 네 가지 I/O 방식을 알아보자. ## 블록킹(Block) vs 넌블록킹(Non-Block) ![image](https://hackmd.io/_uploads/BymRh4gdn.png) - 블록킹과 넌블록킹은 프로세스 또는 스레드가 작업을 수행할 때의 동작 방식을 기술하는 용어다. - 블록킹과 넌블록킹의 차이는 제어권을 돌려받는 시점에 대한 차이다. ### 블록킹(Block) - 블록킹은 호출된 함수가 작업을 완료하고 결과를 반환할 때까지 제어권을 넘겨주고 기다리는 방식 - 이 기간 동안 기존 함수는 작업을 일시 중단(대기 상태)하며 CPU의 제어권은 다른 프로세스나 스레드에게 넘어간다 - 호출된 함수가 작업을 완료하면 그 결과와 함께 제어권을 원래 함수로 돌려주고, 원래 함수는 중단되었던 작업을 계속 진행한다 - 이런 방식은 프로그램의 흐름을 예측하기 쉽게 해주지만, 처리 시간이 긴 함수를 호출할 때는 프로그램의 반응성이 저하될 수 있다 ### 넌블록킹(Non-Block) - 넌블록킹은 함수를 호출했을 때 그 결과를 기다리지 않고 바로 다음 작업을 진행하는 방식 - 제어권을 호출된 함수에게 잠시 넘겨주었다가 바로 다시 돌려받는 형태로 이루어진다 - 이 방식을 사용하면 호출된 함수가 결과를 즉시 반환하지 않더라도 원래 함수는 다른 작업을 계속 진행할 수 있다 - 그러나 이렇게 동시에 여러 작업을 진행하려면 상태 관리가 복잡해질 수 있으며, 코드의 실행 흐름을 따라가기 어려울 수 있다 ## 동기(Synchronous) vs 비동기(Asynchronous) ![image](https://hackmd.io/_uploads/H12GWBxdn.png) ![image](https://hackmd.io/_uploads/HyMVqSxO3.png) - 동기와 비동기는 작업의 순서 또는 작업 간의 시간 관계를 기술하는 용어다. - 동기와 비동기의 차이는 호출되는 함수의 작업 완료를 신경쓰는지의 여부차이이다. - 즉 작업을 수행하는 2개이상의 대상에 대해 다음 작업이 요청되는 시간과 관련이 있다. ### 동기(Synchronous) - 동기식 처리에서는 한 작업이 완전히 끝난 후에 다음 작업이 시작된다. - 즉, 한 작업이 수행되는 동안에는 아무런 다른 처리를 진행하지 않는다. - 예를 들어, 데이터를 불러오는 함수를 호출하는 경우, 해당 함수가 데이터를 모두 불러올 때까지 프로그램은 대기 상태에 머무른다. - 동기 방식에서는 작업의 순서가 명확하므로 이해하고 디버깅하기 쉽지만, 먼저 수행되는 작업이 시간이 오래 걸릴 경우 전체 프로그램의 실행 시간이 길어질 수 있다. - 그 이유는 뒤따르는 작업들이 먼저 수행되는 작업의 완료를 기다려야 하기 때문이다. ### 비동기(Asynchronous) - 비동기식 처리에서는 한 작업이 완료되지 않아도 다음 작업을 시작한다. - 이 방식에서는 일반적으로 작업이 끝나면 그 결과를 알리는 콜백 함수를 사용하거나, 프로미스와 같은 방식을 통해 작업의 결과를 처리한다. - 비동기 방식에서는 여러 작업이 병렬로 수행될 수 있으므로 전체 프로그램의 실행 시간을 줄일 수 있다. 그러나 비동기 코드는 동기 코드보다 복잡하고, 디버깅이 어려울 수 있으며, 여러 비동기 작업을 조율하는 로직을 추가로 구현해야 할 수도 있다. #### Callback 이란 - 콜백 함수(callback function)는 프로그래밍에서 일종의 함수 패턴이다. - 한 함수가 다른 함수를 인자로 받아서 특정 시점에서 호출하는 것을 말한다. - 즉, "나중에 호출해달라"고 하는 함수를 말한다. - 콜백 함수는 특히 이벤트 처리, 비동기 처리, 타이머 작업 등에서 사용된다. - 자바는 Runnable, Future, CompletableFuture를 사용하여 비동기 처리를 간단하게 처리할 수 있다. - Javascript에서는 Promise, async/awiat 사용 ```java public class Main { public static void execute(Runnable callback) { System.out.println("Performing a task..."); callback.run(); } public static void main(String[] args) { execute(() -> System.out.println("Task finished, this is the callback!")); execute(() -> System.out.println("Task Ended!!!")); } } ``` ## 동기(Synchronous) vs 비동기(Asynchronous) (함수의 관점) ![image](https://hackmd.io/_uploads/SJtya4g_n.png) - 함수의 관점에서 동기와 비동기는 함수 호출과 그 결과 처리 방식에 차이를 두고 설명할 수 있다. | 동기(Synchronous) | 비동기(Asynchronous) | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | 함수 호출 후 결과를 기다림 | 함수 호출 후 결과를 기다리지 않음 | | 호출된 함수의 작업이 완료될 때까지 대기 | 호출된 함수의 작업이 완료되지 않아도 다음 작업을 진행 | | 호출한 함수가 직접 결과를 처리 | 호출된 함수가 작업을 완료하면 콜백 함수를 통해 결과를 처리 | | 코드의 실행 순서가 명확하고 이해하기 쉬움 | 코드의 실행 순서가 비직관적이고 이해하기 어려울 수 있음 | | 처리가 끝나는 동안 대기해야 하므로 리소스를 효율적으로 사용하지 못할 수 있음 | 호출한 함수는 다른 작업을 계속 진행할 수 있으므로 리소스를 효율적으로 사용할 수 있음 | ## 블록킹 vs 넌블록킹, 동기 vs 비동기 ![image](https://hackmd.io/_uploads/ByE_LmUbA.png) ### 동기 & 블록킹 - 가장 일반적인 방식으로, 요청이 처리될 때까지 기다린다. - ex) 웹 서버에서 클라이언트 요청을 처리하는 경우, 클라이언트는 응답을 받을 때까지 기다는 작업 - 호출된 함수가 작업을 완료하고 결과를 반환하면, 호출하는 함수는 그 결과를 사용하여 다음 작업을 진행한다. - 이 동안 호출하는 함수는 다른 어떤 작업도 진행하지 않는다. - 이 방식은 코드가 순차적으로 실행되기 때문에 이해하고 디버깅하기 쉽다. - 코드 작성이 비교적 직관적이나, 다수의 동시 사용자가 있는 경우에는 서버의 자원이 효율적으로 사용되지 않을 수 있다. ### 동기 & 넌블록킹 - 요청을 보낸 후, 응답이 오지 않더라도 다른 작업을 계속 수행할 수 있다. - 이 방식은 주로 이벤트 루프와 함께 사용된다. - ex) 자바의 NIO (Non-blocking I/O), Node.js의 이벤트 루프, Python의 asyncio - I/O 작업을 수행하면서 여러 작업을 해야하는 작업이 많이 필요한 애플리케이션에서 효과적이다. ### 비동기 & 블록킹 - 작업을 요청하고, 작업 완료를 알리는 콜백을 등록한다. 콜백은 별도의 스레드에서 실행되는 것이 아니라, 작업 요청자가 블록킹 호출을 통해 명시적으로 확인하는 방식이다 - ex) GUI 애플리케이션에서 긴 작업을 요청하고, 작업 상태를 주기적으로 폴링하는 경우가 이에 해당 - 호출하는 함수는 호출된 함수를 시작하고, 완료 시그널이나 콜백을 기다린다. - 그러나 완료 시그널이나 콜백이 도착할 때까지 다른 작업을 수행하지 않고 대기하는 상태가 된다. - 이러한 패턴은 특별한 경우에만 사용되며, 일반적으로는 비효율적이다. ### 비동기 & 넌블록킹 - 작업 요청과 콜백을 등록한 후, 즉시 다음 작업으로 넘간다. 호출된 함수는 작업을 별도의 스레드나 프로세스에서 수행하며, 작업이 완료되면 완료 시그널이나 콜백을 통해 호출하는 함수에게 알린다. - ex) Java의 Future나, JavaScript의 Promise - 호출하는 함수는 호출된 함수를 시작하고 즉시 다음 작업을 진행하기 때문에, I/O 작업이 많은 경우나 작업 시간이 긴 경우에 효율적이다. - 이 방식은 I/O 작업, 네트워크 요청 등에 효율적이며, 호출하는 함수는 대기 시간 동안 다른 작업을 진행할 수 있어 리소스를 효율적으로 활용할 수 있다. - 그러나 비동기 코드는 동기 코드보다 복잡하고 디버깅하기 어려울 수 있다. > 넌블록킹 동기는 단일 I/O 작업에 대해 효과적이고, 넌블록킹 비동기는 여러 동시 I/O 작업에 대해 효과적이다 ## 번외: 콜백지옥 - 비동기 프로그래밍에서 콜백 함수를 사용할 때 자주 발생하는 문제를 의미한다 - 보통 콜백을 통해 비동기 작업을 할 경우, 콜백의 특성상 비동기 이후에 처리될 작업들을 콜백 내부에 작성해주어야 한다. 이때 콜백 함수를 너무 많이 중첩해서 사용하면 코드의 가독성이 떨어지고, 에러 처리가 복잡해지는 문제가 발생한다 - 자바에서는 비동기 처리를 위해 보통 Future, CompletableFuture 등을 사용하기에, 콜백 패턴을 직접적으로 구현하는 경우는 비교적 드물다. - Javascript에서는 콜백을 자주 사용하기에 종종 콜백지옥을 마딱뜨리게 된다 ```java public class Main { public static void execute(Runnable callback) { System.out.println("작업을 시작합니다"); callback.run(); System.out.println("작업을 종료합니다"); } public static void main(String[] args) { execute(() -> { System.out.println("첫 번째 콜백함수를 실행합니다"); execute(() -> { System.out.println("두 번째 콜백함수를 실행합니다"); execute(() -> { System.out.println("세 번째 콜백함수를 실행합니다"); execute(() -> { System.out.println("네 번째 콜백함수를 실행합니다"); }); }); }); }); } } // [결과] // 작업을 시작합니다 // 첫 번째 콜백함수를 실행합니다 // 작업을 시작합니다 // 두 번째 콜백함수를 실행합니다 // 작업을 시작합니다 // 세 번째 콜백함수를 실행합니다 // 작업을 시작합니다 // 네 번째 콜백함수를 실행합니다 // 작업을 종료합니다 // 작업을 종료합니다 // 작업을 종료합니다 // 작업을 종료합니다 ``` ## 번외: 이벤트 루프 (Event Loop) ![image](https://hackmd.io/_uploads/H115HCWu3.png) - 비동기 이벤트 기반 프로그래밍 모델에서 사용되는 실행 모델 - 단일 스레드에서 이벤트 처리와 비동기 작업을 효율적으로 관리하기 위해 사용된다 - 주로 웹 브라우저나 네트워크 서버 등과 같이 I/O 작업이 빈번하게 발생하는 환경에서 활용된다 - Node.js에서는 이벤트 루프를 이용해 비동기 I/O 작업을 처리한다. 이를 통해 Node.js는 단일 스레드로도 효율적인 동시성을 지원할 수 있다. ### 좀 더 이해하기 쉽게 - 이벤트 루프는 본질적으로 '일 처리기'로 생각할 수 있다. - 간단히 말해서, 이벤트 루프는 계속해서 일을 찾아보고, 할 일이 있다면 그 일을 처리하는 역할을 한다. - 이 '일'은 대개 프로그램에서 발생하는 다양한 이벤트들을 가리키며, 이런 이벤트들은 사용자의 마우스 클릭, 키보드 입력 등 사용자의 액션 또는 프로그램 내부에서 발생하는 시스템 이벤트일 수 있다. 예를 들어 레스토랑의 주방장이 여러 요리사들과 함께 요리고 있다고 가정하자. 각 요리사는 특정 요리를 담당하고, 주방장은 이들 요리사들이 요리를 잘 만들어 내는지 확인하는 역할을 한다 - 고객의 주문(이벤트)이 들어오면, 주문을 받아서 적절한 요리사에게 할당한다(이벤트 핸들링). 요리가 완성되면 고객에게 서빙한다. 이 과정은 고객들로부터 계속 주문이 들어오는 동안 반복되며, 이것이 바로 '루프(loop)'이다. - 요리를 준비하는 데에는 시간이 필요하며, 주방장은 그 동안 다른 일을 할 수 있다. 이것이 바로 '비동기' 처리다. 이처럼 이벤트 루프는 주문(이벤트)이 발생하면 처리하고, 처리하는 동안에도 새로운 이벤트를 계속 감지하며(비동기), 이 일련의 과정을 반복적으로 수행한다(루프). - 만약 주방장이 한 번에 하나의 주문만 받을 수 있고, 요리가 완전히 끝날 때까지 다른 주문을 처리하지 못한다면? 이것은 '동기' 처리 방식이며, 이 경우에는 이벤트 루프가 효율적이지 못하게 된다. 이벤트 루프는 '비동기' 방식에서 특히 빛을 발하며, 여러 이벤트를 효과적으로 처리할 수 있다. 요약하면, 이벤트 루프는 `할 일이 있으면 처리하고, 없으면 기다리다가 새로운 일이 생기면 그것을 처리`하는 방식으로 한다. 이벤트 루프는 프로그램이 사용자의 입력 또는 시스템 이벤트에 대응하여 반응적으로 동작할 수 있게 해준다.