# 블록킹, 넌블록킹, 동기, 비동기
블록킹(Block), 넌블록킹(Non-Block), 동기(Synchronous), 비동기(Asynchronous)는 I/O 작업, 특히 네트워크 통신에서 자주 마주치는 개념이다. 애플리케이션의 효율성과 성능을 결정하는 중요한 요소다. 이 네 가지 I/O 방식을 알아보자.
## 블록킹(Block) vs 넌블록킹(Non-Block)

- 블록킹과 넌블록킹은 프로세스 또는 스레드가 작업을 수행할 때의 동작 방식을 기술하는 용어다.
- 블록킹과 넌블록킹의 차이는 제어권을 돌려받는 시점에 대한 차이다.
### 블록킹(Block)
- 블록킹은 호출된 함수가 작업을 완료하고 결과를 반환할 때까지 제어권을 넘겨주고 기다리는 방식
- 이 기간 동안 기존 함수는 작업을 일시 중단(대기 상태)하며 CPU의 제어권은 다른 프로세스나 스레드에게 넘어간다
- 호출된 함수가 작업을 완료하면 그 결과와 함께 제어권을 원래 함수로 돌려주고, 원래 함수는 중단되었던 작업을 계속 진행한다
- 이런 방식은 프로그램의 흐름을 예측하기 쉽게 해주지만, 처리 시간이 긴 함수를 호출할 때는 프로그램의 반응성이 저하될 수 있다
### 넌블록킹(Non-Block)
- 넌블록킹은 함수를 호출했을 때 그 결과를 기다리지 않고 바로 다음 작업을 진행하는 방식
- 제어권을 호출된 함수에게 잠시 넘겨주었다가 바로 다시 돌려받는 형태로 이루어진다
- 이 방식을 사용하면 호출된 함수가 결과를 즉시 반환하지 않더라도 원래 함수는 다른 작업을 계속 진행할 수 있다
- 그러나 이렇게 동시에 여러 작업을 진행하려면 상태 관리가 복잡해질 수 있으며, 코드의 실행 흐름을 따라가기 어려울 수 있다
## 동기(Synchronous) vs 비동기(Asynchronous)


- 동기와 비동기는 작업의 순서 또는 작업 간의 시간 관계를 기술하는 용어다.
- 동기와 비동기의 차이는 호출되는 함수의 작업 완료를 신경쓰는지의 여부차이이다.
- 즉 작업을 수행하는 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) (함수의 관점)

- 함수의 관점에서 동기와 비동기는 함수 호출과 그 결과 처리 방식에 차이를 두고 설명할 수 있다.
| 동기(Synchronous) | 비동기(Asynchronous) |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| 함수 호출 후 결과를 기다림 | 함수 호출 후 결과를 기다리지 않음 |
| 호출된 함수의 작업이 완료될 때까지 대기 | 호출된 함수의 작업이 완료되지 않아도 다음 작업을 진행 |
| 호출한 함수가 직접 결과를 처리 | 호출된 함수가 작업을 완료하면 콜백 함수를 통해 결과를 처리 |
| 코드의 실행 순서가 명확하고 이해하기 쉬움 | 코드의 실행 순서가 비직관적이고 이해하기 어려울 수 있음 |
| 처리가 끝나는 동안 대기해야 하므로 리소스를 효율적으로 사용하지 못할 수 있음 | 호출한 함수는 다른 작업을 계속 진행할 수 있으므로 리소스를 효율적으로 사용할 수 있음 |
## 블록킹 vs 넌블록킹, 동기 vs 비동기

### 동기 & 블록킹
- 가장 일반적인 방식으로, 요청이 처리될 때까지 기다린다.
- 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)

- 비동기 이벤트 기반 프로그래밍 모델에서 사용되는 실행 모델
- 단일 스레드에서 이벤트 처리와 비동기 작업을 효율적으로 관리하기 위해 사용된다
- 주로 웹 브라우저나 네트워크 서버 등과 같이 I/O 작업이 빈번하게 발생하는 환경에서 활용된다
- Node.js에서는 이벤트 루프를 이용해 비동기 I/O 작업을 처리한다. 이를 통해 Node.js는 단일 스레드로도 효율적인 동시성을 지원할 수 있다.
### 좀 더 이해하기 쉽게
- 이벤트 루프는 본질적으로 '일 처리기'로 생각할 수 있다.
- 간단히 말해서, 이벤트 루프는 계속해서 일을 찾아보고, 할 일이 있다면 그 일을 처리하는 역할을 한다.
- 이 '일'은 대개 프로그램에서 발생하는 다양한 이벤트들을 가리키며, 이런 이벤트들은 사용자의 마우스 클릭, 키보드 입력 등 사용자의 액션 또는 프로그램 내부에서 발생하는 시스템 이벤트일 수 있다.
예를 들어 레스토랑의 주방장이 여러 요리사들과 함께 요리고 있다고 가정하자. 각 요리사는 특정 요리를 담당하고, 주방장은 이들 요리사들이 요리를 잘 만들어 내는지 확인하는 역할을 한다
- 고객의 주문(이벤트)이 들어오면, 주문을 받아서 적절한 요리사에게 할당한다(이벤트 핸들링). 요리가 완성되면 고객에게 서빙한다. 이 과정은 고객들로부터 계속 주문이 들어오는 동안 반복되며, 이것이 바로 '루프(loop)'이다.
- 요리를 준비하는 데에는 시간이 필요하며, 주방장은 그 동안 다른 일을 할 수 있다. 이것이 바로 '비동기' 처리다. 이처럼 이벤트 루프는 주문(이벤트)이 발생하면 처리하고, 처리하는 동안에도 새로운 이벤트를 계속 감지하며(비동기), 이 일련의 과정을 반복적으로 수행한다(루프).
- 만약 주방장이 한 번에 하나의 주문만 받을 수 있고, 요리가 완전히 끝날 때까지 다른 주문을 처리하지 못한다면? 이것은 '동기' 처리 방식이며, 이 경우에는 이벤트 루프가 효율적이지 못하게 된다. 이벤트 루프는 '비동기' 방식에서 특히 빛을 발하며, 여러 이벤트를 효과적으로 처리할 수 있다.
요약하면, 이벤트 루프는 `할 일이 있으면 처리하고, 없으면 기다리다가 새로운 일이 생기면 그것을 처리`하는 방식으로 한다. 이벤트 루프는 프로그램이 사용자의 입력 또는 시스템 이벤트에 대응하여 반응적으로 동작할 수 있게 해준다.