본문 바로가기
Nodejs

[Nodejs] 동기/비동기, 블로킹/논블로킹

by hotdog7778 2023. 10. 2.

동기/비동기, 블로킹/논블로킹은 Nodejs에 한정한 개념이 아니다.

 

블로킹은 일반적으로 스레드 수준에서 발생하며

블로킹 작업은 스레드의 실행을 차단하고, 해당 스레드가 다른 작업을 수행하지 못하게 만든다.

 

Node.js는 싱글 스레드 기반의 런타임 환경으로, 기본적으로 하나의 메인 스레드에서 코드를 실행하는 특징이 있다.

싱글 스레드 환경에서는 한 번에 하나의 작업만을 처리할 수 있고

따라서 메인 스레드에서 코드 실행 중인 경우 다른 작업은 대기해야 함.

 

메인스레드에서 전부 처리할꺼면 싱글스레드방식이 비효율적인데 그래서

비동기+논블로킹 방식으로 일부 작업들을 백그라운드로 보내고 스레드풀의 스레드가 처리할 수 있도록 설계됐고 이게 핵심이다.

 

메인 스레드에서 블로킹 작업을 처리한다?

메인 스레드는 코드 실행, 함수 호출, 이벤트 처리, 비동기 작업 관리, 자원 관리, 스케줄링 등 맡고 있는 업무가 많은데 블로킹된 작업을 처리하는 동안 다른 맡은 업무는 처리를 못하게 된다. 그래서 주로 블로킹 작업을 최소화하고 비동기적인 방식을 활용하여 프로그램의 성능을 향상 시키는게 중요하다.

 

그래서 개발자는  콜백, 프로미스, async/await 등을 사용해서 비동기적 처리를 하는 코드를 작성하고 프로그램의 효율을 높이는데 집중해야 한다.

 

그래도 블록/논블록 <--> 동기/비동기는 헷갈리니까 정리

블로킹은 작업을 차단하고 기다리는 것이며, 동기는 순차적으로 실행되는 것, 

논블로킹은 작업을 계속하면서 다른 작업을 실행할 수 있는 것, 비동기는 작업이 백그라운드에서 수행되고, 작업이 완료되면 결과를 콜백 함수나 Promise와 같은 메커니즘을 사용하여 처리하는 것

* 순차적: 의도한 순서대로 실행되는 것. 예를 들어, A 작업이 B 작업보다 먼저 실행되고, 그 다음에 B 작업이 실행되는 것이 순차적인 실행

 

작업을 차단하고 기다리는 것과 순차적으로 실행되는 것은 같은것 아닌가?

 

 - 작업을 차단하고 기다리는 것: 일반적으로 한 작업이 완료될 때까지 다른 작업을 아무것도 수행하지 않고 대기하는 것을 의미. 블로킹 작업은 해당 작업이 끝날 때까지 아무 것도 진행하지 않는다. 예를 들어, 파일을 동기적으로 읽는 작업은 해당 파일이 읽힐 때까지 다른 작업을 수행하지 않고 대기하는 블로킹 작업이다.

 - 순차적으로 실행되는 것: 작업이 한 번에 하나씩 순차적으로 실행되는 것을 의미합니다. 즉, 한 작업이 끝나면 그 다음 작업이 실행되는 것. 이는 동기적인 방식의 코드 실행을 나타낸다. 순차적 실행은 블로킹이 발생하지 않을 수도 있고 발생할 수도 있다.

 

 

비동기 작업을 순차적으로 처리해야 할때

비동기함수 fetchAPI1, fetchAPI2, fetchAPI3 가 있다.

 

// 비동기 함수 fetchAPI1
async function fetchAPI1() {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  const data = await response.json();
  return data;
}

// 비동기 함수 fetchAPI1
async function fetchAPI2() {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/2');
  const data = await response.json();
  return data;
}

// 비동기 함수 fetchAPI3
async function fetchAPI3() {
  const response = await fetch('https://api.github.com/repos/openai/gpt-3');
  const data = await response.json();
  return data;
}

function fetchData() {
  const result1Promise = fetchAPI1();
  const result2Promise = fetchAPI2();
  const result3Promise = fetchAPI3();

  result1Promise.then((result1) => {
    console.log('111111111111', result1);
  });

  result2Promise.then((result2) => {
    console.log('2222222222', result2);
  });

  result3Promise.then((result3) => {
    console.log('33333333333', result3);
  });
}

function something() {
  console.log('메롱');
}

fetchData();
something();

 - fetchAPI1 , 2 , 3 모두 백그라운드에서 처리되고 각자의 처리완료 속도에 콜백함수를 실행시킬것이다. (순차적이지 않음)

 1) fetchData() 함수가 스택으로 이동하고, fetchAPI 1,2,3 함수는 백그라운드에서 실행되며 데이터를 가져오기 위해 네트워크 요청

 2) fetchData() 함수는 스택에서 빠지고, 네트워크 작업이 완료되어 resolved 된 promise의 콜백이 먼저 스택으로 이동되어 처리된다.

 - 실제로 메롱,3,2,1  순서대로 출력됐다. API 응답시간을 따로 측정했을때도 1 = 433ms, 2 = 431ms, 3 = 394ms

 

 

 

 

async function fetchData() {
  try {
    const result1 = await fetchAPI1();
    console.log(result1);

    const result2 = await fetchAPI2();
    console.log(result2);

    const result3 = await fetchAPI3();
    console.log(result3);
  } catch (error) {
    console.error('에러 발생:', error);
  }
}

function something() {
  console.log('메롱')
}

fetchData();
something();

 - 첫번째 코드를 순차적으로 실행하도록 바꿔야 하는 경우가 생겼을때 ( 1 -> 2 -> 3 순서를 지켜)

 - await에 의해 블로킹 된다.

 - fetchAPI1 , 2 , 3 이 순차적으로 처리된다. (실행->완료->실행->완료..)

 1) fetchData 라는 비동기 함수를 호출한다. fetchData() 는 스택에 추가된다.

 2) fetchData 함수의 body 부분이 동작한다. fetchAPI1() 는 스택에 추가된다.

 3) fetchAPI1() 가 실행되고, promise를 리턴한다.

 4) 이때, 엔진은 await를 마주하고 비동기함수 fetchData는 지연된다. (스택에서 큐로 이동)

 5) 전역 컨텍스트에 선언된 something() 함수가 스택으로 이동하며, 함수가 실행되어 console.log 출력

 6) 스택이 비었다. fetchData()는 스택으로 이동 fetchAPI1()의 결과를 console.log로 출력한다.

 7) fetchAPI2() 가 실행되고, promise를 리턴한다.

 8) await를 마주하고 비동기함수 fetchData는 지연된다. (스택에서 큐로 이동)

 9) 스택이 비었다. fetchData()는 스택으로 이동 fetchAPI2()의 결과를 console.log로 출력한다.

 10) ..

 11) ..

 

 

 

 

 

-끝-