[Node.js] Blocking과 Non-Blocking

Main·2024년 9월 30일
0

Node.js

목록 보기
10/20
post-thumbnail

Blocking

블로킹(Blocking) 작업은 하나의 작업이 완료될 때까지 다음 작업이 대기하는 방식입니다. 예를 들어, 파일 읽기 작업이 블로킹 방식이라면 해당 파일이 완전히 읽힐 때까지 프로그램의 흐름이 멈추게 됩니다. 이는 코드가 순차적으로 실행되며, 각 작업이 다음 작업으로 넘어가기 전에 반드시 완료되어야 한다는 것을 의미합니다.

const fs = require('fs');

try {
    const data = fs.readFileSync('example.txt', 'utf8');
    console.log("블로킹 파일 내용:", data);
} catch (err) {
    console.error("오류 발생:", err);
}

동기(Synchronous) 방식과의 차이점

  • 블로킹(Blocking) : 블로킹은 작업이 완료될 때까지 코드 실행이 멈추는 것을 의미합니다. 즉, 어떤 작업을 처리하는 동안 다른 작업이 기다려야 하며, 제어권이 그 작업에 의해 차단되는 것입니다. 주로 I/O 작업에서 사용되며, 실행 흐름을 가로막아 자원을 비효율적으로 사용할 수 있습니다.
  • 동기(Synchronous) : 동기는 작업을 순서대로 실행하는 방식을 의미합니다. 이전 작업이 완료되어야 다음 작업을 진행합니다. 동기 방식의 코드는 일반적으로 순차적이며 직관적입니다. 동기는 작업 간의 순차적 관계에 초점을 맞추고 있습니다.

주요 차이점

  • 블로킹작업을 완료할 때까지 제어권을 잡고 있는지 여부와 관련됩니다.
  • 동기코드 실행 순서에 관한 것입니다.

Non-blocking

논블로킹(Non-blocking) 작업은 작업의 완료 여부와 관계없이 즉시 제어권을 반환하는 방식입니다. Node.js의 대부분의 I/O 작업은 논블로킹 방식으로 동작합니다. 작업이 완료되지 않더라도 즉시 다음 코드를 실행할 수 있도록 하는 것입니다. 이 방식 덕분에 Node.js는 동시에 많은 작업을 처리할 수 있어 효율적인 서버로 사용할 수 있습니다.

const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error("오류 발생:", err);
    } else {
        console.log("논블로킹 파일 내용:", data);
    }
});

console.log("논블로킹 파일 읽기 요청이 완료되었습니다.");

비동기(Asynchronous) 방식과의 차이점

  • 논블로킹(Non-blocking) : 논블로킹은 작업 요청 후 제어권을 즉시 반환하는 것을 의미합니다. 작업이 끝날 때까지 기다리지 않고 다른 작업을 처리할 수 있습니다. 이는 CPU가 다른 작업을 처리하도록 하여 자원 효율을 극대화합니다. 주로 Node.js의 I/O 작업이 논블로킹 방식으로 이루어집니다.
  • 비동기(Asynchronous) : 비동기는 작업이 완료된 시점에 콜백이나 프로미스 등을 통해 결과를 처리하는 방식을 의미합니다. 비동기 방식에서는 작업의 요청과 결과 처리 사이에 시간적 비동기성이 존재합니다. 작업이 끝난 후의 처리를 위해 콜백, 프로미스, 혹은 async/await 등을 사용합니다.

주요 차이점

  • 논블로킹작업의 제어권을 즉시 반환하고, 다른 작업을 계속할 수 있는지 여부에 초점을 맞춥니다.
  • 비동기작업 완료 후의 처리 방법에 초점을 맞춥니다.

Blocking/Non-Blocking + Synchronsous/Asynchronous 조합

1 ) Synchronous + Blocking 조합

Synchronous + Blocking 조합은 다른 작업이 진행되는 동안 자신의 작업을 처리하지 않고 (Blocking), 다른 작업의 완료 여부를 바로 받아 순차적으로 처리하는 동기 방식입니다. 다른 작업의 결과가 자신의 작업에 영향을 주는 경우에 활용할 수 있습니다.


실생활 예시

팀장은 사원 1에게 업무 A를 지시하고, 사원 1이 업무를 마칠 때까지 아무 일도 하지 않고 기다립니다. 업무 A가 끝나야만 팀장은 다음 사원에게 다음 작업을 지시할 수 있습니다.

팀장: 사원1씨, 업무 A를 해주세요.
사원1: (업무 A를 수행 중...)
팀장: (사원1이 끝날 때까지 기다림)
사원1: 업무 A 완료했습니다!
팀장: 수고했어요. 이제 사원2씨, 업무 B를 해주세요.
사원2: (업무 B 수행 중...)

예시 코드

아래 코드에서 while 루프를 사용해 2초 동안 블로킹을 시킵니다. task1이 완료될 때까지 task2는 실행되지 않습니다.

function task1() {
  console.log("Task 1 시작");
  let end = Date.now() + 2000; // 2초 동안 블로킹
  while (Date.now() < end) {}  // 작업이 끝날 때까지 기다림
  console.log("Task 1 완료");
}

function task2() {
  console.log("Task 2 시작");
  let end = Date.now() + 2000; // 2초 동안 블로킹
  while (Date.now() < end) {}  // 작업이 끝날 때까지 기다림
  console.log("Task 2 완료");
}

task1(); // Task 1이 끝날 때까지 기다림
task2(); // Task 1이 끝난 후에 시작

2 ) Asynchronous + Non-Blocking 조합

Asynchronous + NonBlocking 조합은 다른 작업이 진행되는 동안에도 자신의 작업을 처리하고 (Non Blocking), 다른 작업의 결과를 바로 처리하지 않아 작업 순서가 지켜지지 않는 비동기 방식입니다. 다른 작업의 결과가 자신의 작업에 영향을 주지 않은 경우에 활용할 수 있습니다. 자바스크립트에서는 setTimeout, Promise 등의 비동기 함수를 사용하여 이를 구현할 수 있습니다.


실생활 예시

팀장은 사원들에게 다른 업무를 동시에 지시 할 수 있습니다. 팀장은 동시에 사원 1에게 업무 A를 지시하고, 사원 2에게 업무 B를 지시합니다. 각 사원은 동시에 일을 진행하고, 나중에 자신의 작업이 완료되면 결과를 팀장에게 보고합니다.

팀장: 사원1씨, 업무 A를 해주세요.
팀장: 사원2씨, 업무 B를 해주세요.
팀장: 사원3씨, 업무 C도 해주세요.
(사원들은 각각 자기 업무를 진행 중...)
사원1: 업무 A 완료했습니다!
사원2: 업무 B 완료했습니다!
사원3: 업무 C 완료했습니다!

예시 코드

아래 코드에서 setTimeout 함수는 비동기적으로 실행되기 때문에 블로킹 없이 동시에 여러 작업 실행이 가능합니다. 때문에 "작업 진행 중…"이 바로 출력됩니다.

console.log("작업 시작");

setTimeout(() => {
  console.log("첫번째 작업 완료");
}, 2000);  // 2초 후 실행 (Non-Blocking)

setTimeout(() => {
  console.log("두번째 작업 완료");
}, 3000);  // 2초 후 실행 (Non-Blocking)

setTimeout(() => {
  console.log("세번째 작업 완료");
}, 4000);  // 2초 후 실행 (Non-Blocking)

console.log("작업 진행중...")

3 ) Synchronous + Non-Blocking 조합

Synchronous + Non-Blocking 조합은 다른 작업이 진행되는 동안에도 자신의 작업을 처리하고 (Non Blocking), 다른 작업의 결과를 바로 처리하여 작업을 순차대로 수행 하는 동기 방식입니다.

자바스크립트 경우에는 Synchronous + Non-Blocking 코드를 구현하기에는 지원하는 메서드에 한계가 있어 완벽히 표현할 수는 없습니다. 다만 이와 비슷하게 async/await 키워드를 사용하면 동기 + 논블로킹 코드를 표현할 수 있습니다. 기본적으로 Promise.then 핸들러 방식은 비동기 논블로킹 방식이라 볼 수 있으며, 비동기 작업들 간의 순서가 중요하다면 await 키워드를 통해 동기적으로 처리해 줄 수 있습니다.


실생활 예시

팀장은 사원 1에게 업무 A를 지시한 후, 업무 A가 끝날 때까지 다른 일을 하지 않고 기다리지는 않습니다. 그러나 팀장은 각 사원이 맡은 작업이 끝나야 다음 사원에게 업무를 지시할 수 있습니다.

팀장: 사원1씨, 업무 A를 해주세요.
(다른 일을 하며 대기 중...)
사원1: 업무 A 완료했습니다!
팀장: 이제 사원2씨, 업무 B를 해주세요.
(다른 일을 하며 대기 중...)
사원2: 업무 B 완료했습니다!
팀장: 수고했어요. 사원3씨, 업무 C를 해주세요.

활용 예시 프로그램

게임에서 맵을 이동할때를 생각해봅시다. 우선 맵 데이터를 모두 다운로드 해야 합니다. 그동안 화면에는 로딩 스크린이 뜨게될 것 입니다. 이 로딩 스크린은 로딩바가 채워지는 프로그램이 수행하고 있는 것입니다. 즉, 제어권은 여전히 나한테 있어 화면에 로드율이 표시되는 것입니다. 그리고 끊임없이 맵 데이터가 어느정도 로드가 됬는지 끊임없이 조회합니다. 자신의 작업을 계속하고 있지만 다른 작업과의 동기를 위해 계속해서 다른 작업이 끝났는지 조회하는 것입니다.


예시 코드

아래 코드에서 awaitdelay 함수가 완료될 때까지 대기합니다. await로 인해 각 작업은 순차적으로 실행되지만, delay 자체는 비동기 메서드이기 때문에 블로킹은 발생하지 않습니다. 이는 동기적인 흐름을 유지하면서도 논블로킹 특성을 갖는 예시입니다.

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function syncNonBlocking() {
  console.log("첫 번째 작업 시작");
  await delay(2000);  // 2초 대기 (await 사용, 동기적 순서)
  console.log("첫 번째 작업 완료");

  console.log("두 번째 작업 시작");
  await delay(2000);
  console.log("두 번째 작업 완료");
}

syncNonBlocking();

💡 Synchronous+ Blocking vs Synchronous+ Non-Blocing

Blocking, Non-Blocking이든 결국 코드를 동기적으로 실행하기 때문에 실제로 작업의 최종 시간은 차이가 없어 보입니다. 그럼 두 방식은 비슷할까요?
성능 차이는 상황에 따라 다르지만, 일반적으로 Synchronous + Non-BlockingSynchronous + Blocking보다 효율적일 수 있습니다. Synchronous + Non-Blocking은 호출하는 함수가 제어권을 가지고 있어서 다른 작업을 병렬적으로 수행할 수 있기 때문입니다. 반면에 Synchronous + Blocking은 호출하는 함수가 제어권을 잃어서 다른 작업을 수행할 수 없기 때문입니다.

Synchronous + Blocking

  • 동기적으로, 작업이 끝날 때까지 기다린 후 다음 작업을 진행하는 방식입니다. 또한 블로킹의 특성 때문에 현재 작업이 완료될 때까지 다른 작업을 진행하지 못하고 기다립니다.
  • 순차적인 흐름을 유지하지만, 각 작업이 완료될 때까지 다른 모든 작업이 중단되므로 효율성이 떨어질 수 있습니다.

Synchronous + Non-Blocking

  • 동기적이지만, 논블로킹의 특성을 가지고 있습니다. 즉, 작업의 순서는 유지하되 각 작업이 다른 작업을 기다리지 않고 제어권을 반환할 수 있습니다.
  • 작업 간의 순서는 지켜지지만, 각 작업이 완료되기를 기다리지 않기 때문에 제어권이 다른 작업으로 넘어갈 수 있는 방식입니다.

4 ) Asynchronous + Blocking 조합

Asynchronous + Blocking 조합은 다른 작업이 진행되는 동안 자신의 작업을 멈추고 기다리는 (Blocking), 다른 작업의 결과를 바로 처리하지 않아 순서대로 작업을 수행하지 않는 (Asynchronous) 방식입니다. 하지만 실무에서는 잘 사용되지 않는 방식입니다.


활용 예시 프로그램

Node.js + MySQL의 조합이 대표적이며, Node.js에서 비동기 방식으로 데이터베이스에 접근하기 때문에 Async 이지만, MySQL 데이터베이스에 접근하기 위한 MySQL 드라이버가 블로킹 방식으로 작동되기 때문입니다.

  • JavaScript는 비동기 방식으로 MySQL에 쿼리를 보냅니다. (Asynchronous)
  • MySQL은 쿼리를 처리하면서 JavaScript에게 제어권을 넘겨주지 않습니다. (Blocking)
  • 그러면 JavaScript는 다른 작업을 계속 수행할 수 있지만, MySQL의 결과값을 필요로 하기 때문에 MySQL이 쿼리를 완료할 때까지 기다려야 됩니다.
  • 결국 Synchronous Blocking과 작업 수행에 차이가 없게 됩니다.

이렇게 JavaScript와 MySQL의 조합은 비동기적이면서도 블로킹되는 Asynchronous + Blocking 조합이라고 할 수 있습니다. 이러한 오묘한 조합은 오히려 개발자에게 혼동만 일으키기 때문에 그래서 실무에서는 Node.js 서버 프로그래밍할때 아예 async/await로 동기 처리를 하는 편입니다.


예시 코드

여기서 awaitasyncBlockingTask가 완료될 때까지 대기하므로 블로킹 효과를 줍니다. 하지만 이 작업은 비동기적으로 실행되기 때문에 setTimeout이 호출된 이후 2초 동안은 다른 비동기 작업이 있을 경우 그것들을 동시에 처리할 수 있게 됩니다.

function asyncBlockingTask() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("비동기 블로킹 작업 완료");
      resolve();
    }, 2000);
  });
}

async function executeBlocking() {
  console.log("첫 번째 비동기 블로킹 작업 시작");
  await asyncBlockingTask();  // 2초 동안 대기 (Blocking 효과)
  console.log("다음 작업 진행");
}

executeBlocking();

참고 사이트

https://inpa.tistory.com/entry/👩‍💻-동기비동기-블로킹논블로킹-개념-정리

profile
함께 개선하는 프론트엔드 개발자

0개의 댓글