728x90

동기 I/O(Synchronous I/O)는 입력 또는 출력 작업이 실행될 때 프로그램의 실행이 그 작업이 완료될 때까지 차단(Block)되는 방식을 의미한다. 즉, 동기 I/O 작업을 요청하면 해당 작업이 완전히 끝나야만 다음 코드 라인이 실행된다. 그리고 프로그램의 실행 흐름이 소스 코드의 순서와 일치하기 때문에, 코드를 읽고 이해하기 쉽다.

 

Blocking I/O에서는 I/O 작업(예: 파일 읽기, 네트워크를 통한 데이터 수신 등)을 요청할 때, 해당 작업이 완료될 때까지 해당 요청을 한 프로세스 또는 스레드가 대기 상태에 들어간다. 즉, 작업이 완료될 때까지 아무것도 하지 못하고 기다려야 한다. 이 방식은 프로그래밍 모델이 단순하고 직관적이지만, I/O 작업의 대기 시간 동안에 CPU가 다른 유용한 작업을 수행하지 못하게 하여 자원을 낭비할 수 있다. (동기 I/O는 I/O 작업 중에 CPU가 유휴 상태가 될 수 있으므로 리소스 활용 측면에서 비효율적일 수 있다.)

 

동기 I/O 방식을 썼을 때 일어나는 일이 뭐가 있을까?

  1. 프로그램이 파일 시스템에서 데이터를 읽거나 쓸 때, 해당 작업이 완료될 때까지 다음 작업으로 넘어가지 않는다.
  2. 데이터베이스에 쿼리를 요청하고, 그 결과를 받기 전까지 다른 작업을 진행하지 않는 경우가 생긴다.
  3. 웹 요청을 보내고 응답을 받을 때까지 기다리는 작업 등이 동기 I/O 방식의 예이다.

비동기 I/O와 비교할 때, 동기 I/O는 구현의 단순함과 코드의 직관적인 흐름 때문에 작은 규모의 애플리케이션 또는 I/O 작업의 복잡성이 낮은 경우에 적합하다. 반면, 비동기 I/O는 I/O 작업이 백그라운드에서 수행되어 프로그램의 응답성과 성능을 향상시킬 수 있는 환경에서 유리하다.

 

비동기 I/O(Asynchronous I/O)는 I/O 작업이 시작된 후, 그 작업이 완료될 때까지 프로그램의 실행이 대기하지 않고 바로 다음 작업으로 넘어가는 방식이다. 이는 I/O 작업이 백그라운드에서 처리되며, 작업의 완료와 데이터의 반환을 대기하는 동안 프로그램이 다른 작업을 수행할 수 있게 해준다.

 

비동기 I/O는 비차단(Non-blocking) 방식을 활용하여 I/O 작업이 프로그램의 실행 흐름을 차단하지 않는다. 프로그램은 I/O 작업의 완료를 기다리지 않고 즉시 다음 코드를 실행할 수 있다.

 

Non-blocking I/O에서는 I/O 작업을 요청하더라도 해당 작업이 즉시 완료될 수 없을 때 대기하지 않고 바로 제어권이 호출자에게 반환된다. 이는 I/O 작업이 완료되지 않았다는 신호와 함께, 프로세스 또는 스레드가 다른 작업을 계속 진행할 수 있게 해준다. Non-blocking I/O는 I/O 작업의 완료를 폴링(polling)하거나, 이벤트 기반 모델을 사용하여 알아차리게 된다. 이 방식은 리소스를 더 효율적으로 활용할 수 있지만, 작업 완료를 확인하고 관리하기 위한 추가적인 로직이 필요하다.

 

Non-blocking I/O를 효과적으로 활용하기 위해서는 비동기 프로그래밍 모델, 이벤트 루프, 콜백 함수, 프로미스, async/await 등과 같은 개념과 기술을 이해하고 적용할 필요가 있다. 이러한 방식은 Node.js와 같은 플랫폼에서 널리 사용되며, 고성능 네트워크 서버와 애플리케이션을 구축하는 데 중요한 역할을 한다.

 

콜백 함수는 다른 함수에 인자로 전달되는 함수로, 어떤 이벤트가 발생했거나 특정 작업이 완료된 후에 실행된다. 이를 통해 비동기 작업을 처리할 수 있다. 그러나 콜백 함수를 사용하여 여러 비동기 작업을 연속으로 처리하려고 할 때, 코드는 점점 중첩되고 가독성이 떨어지는 문제가 발생한다. 이러한 상황을 콜백 지옥(Callback Hell) 또는 콜백 피라미드(Pyramid of Doom)라고 한다.

getData(function(a){
    getMoreData(a, function(b){
        getMoreData(b, function(c){ 
            console.log('콜백 지옥');
        });
    });
});

 

프로미스는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체다. 콜백 지옥의 문제를 해결하기 위해 ES6에서 도입되었다. 프로미스는 .then() 메서드를 통해 연속된 비동기 작업을 체이닝할 수 있게 해준다. 또한, .catch() 메서드를 통해 에러를 쉽게 처리할 수 있다.

getData()
  .then(result => getMoreData(result))
  .then(result => getMoreData(result))
  .then(finalResult => console.log(finalResult))
  .catch(error => console.error(error));

 

async/await는 ES2017(ES8)에서 소개된, 비동기 작업을 동기적으로 보이는 코드 스타일로 작성할 수 있게 해주는 문법이다. async 함수 내부에서 await 키워드를 사용하면, 프로미스가 처리될 때까지 함수의 실행을 일시 중단하고, 프로미스의 결과가 반환되면 실행을 재개한다. 이를 통해 비동기 코드를 마치 동기 코드처럼 쉽게 작성하고 이해할 수 있다.

async function asyncCall() {
  try {
    const result = await getData();
    const moreData = await getMoreData(result);
    const finalResult = await getMoreData(moreData);
    console.log(finalResult);
  } catch (error) {
    console.error(error);
  }
}

 

 

비동기 프로그래밍 모델의 장접은 애플리케이션의 응답성을 향상시켜주고, I/O 작업 중에도 CPU는 다른 작업을 처리할 수 있으므로, 자원을 보다 효율적으로 활용할 수 있다. 또한, 여러 I/O 작업을 동시에 처리할 수 있어, 애플리케이션의 처리 능력을 향상시킬 수 있다.

 

하지만 비동기 프로그래밍 모델은 동기 모델에 비해 이해하고 사용하기가 더 복잡할 수 있고 콜백 지옥과 같은 문제를 관리해야 하는 단점이 있다.

728x90

+ Recent posts