Nodejs 이벤트루프, 논블로킹, 비동기

Collin·2022년 11월 12일
0
  • INTRO

nodejs - 크롬 V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임이다. 이벤트 기반, 논블로킹 I/O모델을 사용해서 가볍고 효율적이다

(자바스크립트로 코딩 돌아가게 해주는 고마운 놈)

코드가 실행되는 내부과정

큰 틀에서 우리가 흔히 아는 스택구조에 함수들이 push된다

하지만 중간에 만약 실행시간이 오래 걸리는 함수가 있을 경우 이후에 실행되는 함수가 딜레이되기 시작한다.

이 같은 경우를 방지하기 위해서 콜백이라는 개념을 사용한다.

  • EX)
function run(){

console.log('3초 후 실행');

}

console.log('시작');

setTimeout(run,3000);

console.log('끝');

======콘솔======

시작

3초 후 실행

setTimeout(run,3000)은 3000ms이후에 코드를 실행한다 즉, run을 콜백으로 던진다는 라인이다

오랜시간을 소요하는 테스크를 시뮬레이션하기 위함이다.

실행결과에서 알 수 있듯이 콜백으로 던진 run이 실행되기 전에 console.log('끝')이 찍힌다

그렇다면 setTimeout이 던진 콜백인 run이 내부적으로 호출스택에 언제 들어가는지 알아보자

이를 알기 위해서는 이벤트 루프, 태스크 큐, 백그라운드 3가지 개념이 필요하다

이벤트 루프

  • 이벤트 발생시 호출할 콜백함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정하는 역할
  • 노드가 종료 될 때 까지 이벤트 처리 위한 작업을 반복한다

태스크 큐

  • 이벤트 발생 후 호출되어야 할 콜백 함수들이 기다리는 공간
  • 콜백들이 이벤트 루프가 정한 순서대로 줄을 서있으므로 콜백 큐라고도 한다

백그라운드

  • 타이머나 I/O 작업 콜백 또는 이벤트 리스너들이 대기하는 곳이다

  • EX2)

호출스택에 함수들이 push 되고 실행된다

setTimeout(run,3000) 백그라운드로 이동하고

3초 뒤에 백그라운드에서 run을 태스크 큐에 넘긴다

호출 스택이 다 비워질 때 까지 대기한다

호출 스택이 다 비워지고 난 뒤에 태스크 큐에 있는 콜백들은 다시 호출 스택에 들어가게 된다

이와 같은 과정을 이벤트 루프라고 한다.

  • CF)

백그라운드에서 3초 후에 run을 태스크 큐에 넘겨도

만약 호출 스택에 함수들이 너무 많이 차 있다면 run이 바로 호출 스택에 들어가지 못한다

즉, setTimeout의 시간이 정확하지 않을 수도 있다.

부제 : 논블로킹

사실 이 같은 과정이 논블로킹 방식과 매우 흡사하다

백그라운드를

  • 타이머나 I/O 작업 콜백 또는 이벤트 리스너들이 대기하는 곳이다

이와 같이 설명하였지만,

오래 걸리는 함수를 백그라운드로 보내서 다음 코드 먼저 실행되고

오래 걸리는 함수가 다시 태스크 큐를 거쳐 호출 스택으로 올라오기 기다리는 방식이

논블로킹 방식이다

  • EX3)

블로킹 방식

function longRunningTask(){

// 오래 걸리는 작업

console.log('작업 끝');

}

console.log('시작');

longRunningTask();

console.log('다음 작업');

======콘솔=====

시작

작업 끝

다음 작업

논블로킹방식

function longRunningTask(){

// 오래 걸리는 작업

console.log('작업 끝');

}

console.log('시작');

setTimeout(longRunningTask,0);

console.log('다음 작업');

======콘솔=====

시작

다음 작업

작업 끝

setTimeout(longRunningTask,0);은 0초후에 실행된다

즉, 바로 실행된다 위 아래의 코드가 왜 다른가라고 생각할 수 도 있는데

앞의 이벤트 루프를 이해했다면 그렇지 않을 것이다

이해하지 못했다면 이벤트 루프 부분을 다시 한번 이해해보자

동기와 비동기

우리가 앞서 익혀왔던 내용들은 비동기-논블로킹에 관한 이야기이다 비교하기 위해

동기-블로킹이야기도 할 예정인데 혼돈되지 않길 바란다.

동기, 비동기, 블로킹, 논블로킹 이 네가지 용어들은 노드에서 혼용되어서 사용된다

하지만 용어에 차이가 있듯이 의미에 차이도 분명히 존재한다

굉장히 잘 정리되어 있는 글이 있어 참조한다

https://deveric.tistory.com/99

비동기와 동기 블로킹 논블로킹 굉장히 잘 설명되어 있는 글이다

저 글을 숙지하고 다음 단계로 넘어가 볼까한다

다음은 비동기-논블로킹이 굉장히 효율적인 측면에서 좋아서 장점은 사용하고 싶은데

단점이 콜백 패턴의 처리 순서를 보장해 주지 않는다

  • EX4)

     예시로 비동기 매서드중 하나인 fs.readFile을 사용해보겠다
const fs = require('fs');

console.log('시작');

fs.readFile('./readme2.txt',(err,data) => {
	if (err){
		throw err;
	}
	console.log('1번', data.toString());
});

fs.readFile('./readme2.txt',(err,data) => {
	if (err){
		throw err;
	}
	console.log('2번', data.toString());
});

fs.readFile('./readme2.txt',(err,data) => {
	if (err){
		throw err;
	}
	console.log('3번', data.toString());
});

console.log('끝');

콘솔

시작

2번

3번

1번

앞서 배운 지식을 토대로 순서 결과가 시작, 끝, (1,2,3) 이정도는 예상이 가능할텐데

2,1,3이 지 멋대로 나오는데 정상이다 이는 반복할 때 마다 결과가 계속 바뀔 것이다

그래서 이 같이 콜백의 처리 순서 보장을 위해 사용하는 여러가지 방법들이 있다

아예 비동기적인 메서드가 아닌 동기적인 메서드를 사용하기도 하고

아니면 흔히 콜백지옥이라고 부르는 아래의 방법을 하기도 한다

  • 콜백지옥
const fs = require('fs');

console.log('시작');

fs.readFile('./readme2.txt',(err,data) => {
	if (err){
		throw err;
	}
	console.log('1번', data.toString());
	fs.readFile('./readme2.txt',(err,data) => {
		if (err){
			throw err;
		}
		console.log('2번', data.toString());
		fs.readFile('./readme2.txt',(err,data) => {
			if (err){
				throw err;
			}
			console.log('3번', data.toString());
		});
	});
});

console.log('끝');

호출스택에 있는 readfile이 파일요청을 백그라운드에 보내고

백그라운드에서 태스크 큐로 콜백이 넘어가고

콜백이 호출스택에 쌓이는데 호출스택에 쌓인 함수를 실행하니

다시 위와 같은 과정을 반복하는 것이다 물론 콜백 순서는 보장될지 언정 가독성과

코드가 너무 꼬여서 수정시 굉장히 어렵다

그래서 우리는 promise를 통해서 이를 해결한다
이를 알기 위해서는 이벤트 루프, 태스크 큐, 백그라운드 3가지 개념이 필요하다

이벤트 루프

  • 이벤트 발생시 호출할 콜백함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정하는 역할
  • 노드가 종료 될 때 까지 이벤트 처리 위한 작업을 반복한다

태스크 큐

  • 이벤트 발생 후 호출되어야 할 콜백 함수들이 기다리는 공간
  • 콜백들이 이벤트 루프가 정한 순서대로 줄을 서있으므로 콜백 큐라고도 한다

백그라운드

  • 타이머나 I/O 작업 콜백 또는 이벤트 리스너들이 대기하는 곳이다

  • EX2)

호출스택에 함수들이 push 되고 실행된다

setTimeout(run,3000) 백그라운드로 이동하고

3초 뒤에 백그라운드에서 run을 태스크 큐에 넘긴다

호출 스택이 다 비워질 때 까지 대기한다

호출 스택이 다 비워지고 난 뒤에 태스크 큐에 있는 콜백들은 다시 호출 스택에 들어가게 된다

이와 같은 과정을 이벤트 루프라고 한다.

  • CF)

백그라운드에서 3초 후에 run을 태스크 큐에 넘겨도

만약 호출 스택에 함수들이 너무 많이 차 있다면 run이 바로 호출 스택에 들어가지 못한다

즉, setTimeout의 시간이 정확하지 않을 수도 있다.

부제 : 논블로킹

사실 이 같은 과정이 논블로킹 방식과 매우 흡사하다

백그라운드를

  • 타이머나 I/O 작업 콜백 또는 이벤트 리스너들이 대기하는 곳이다

이와 같이 설명하였지만,

오래 걸리는 함수를 백그라운드로 보내서 다음 코드 먼저 실행되고

오래 걸리는 함수가 다시 태스크 큐를 거쳐 호출 스택으로 올라오기 기다리는 방식이

논블로킹 방식이다

  • EX3)

블로킹 방식

function longRunningTask(){

// 오래 걸리는 작업

console.log('작업 끝');

}

console.log('시작');

longRunningTask();

console.log('다음 작업');

======콘솔=====

시작

작업 끝

다음 작업

논블로킹방식

function longRunningTask(){

// 오래 걸리는 작업

console.log('작업 끝');

}

console.log('시작');

setTimeout(longRunningTask,0);

console.log('다음 작업');

======콘솔=====

시작

다음 작업

작업 끝

setTimeout(longRunningTask,0);은 0초후에 실행된다

즉, 바로 실행된다 위 아래의 코드가 왜 다른가라고 생각할 수 도 있는데

앞의 이벤트 루프를 이해했다면 그렇지 않을 것이다

이해하지 못했다면 이벤트 루프 부분을 다시 한번 이해해보자

동기와 비동기

우리가 앞서 익혀왔던 내용들은 비동기-논블로킹에 관한 이야기이다 비교하기 위해

동기-블로킹이야기도 할 예정인데 혼돈되지 않길 바란다.

동기, 비동기, 블로킹, 논블로킹 이 네가지 용어들은 노드에서 혼용되어서 사용된다

하지만 용어에 차이가 있듯이 의미에 차이도 분명히 존재한다

굉장히 잘 정리되어 있는 글이 있어 참조한다

https://deveric.tistory.com/99

비동기와 동기 블로킹 논블로킹 굉장히 잘 설명되어 있는 글이다

저 글을 숙지하고 다음 단계로 넘어가 볼까한다

다음은 비동기-논블로킹이 굉장히 효율적인 측면에서 좋아서 장점은 사용하고 싶은데

단점이 콜백 패턴의 처리 순서를 보장해 주지 않는다

  • EX4)

     예시로 비동기 매서드중 하나인 fs.readFile을 사용해보겠다
const fs = require('fs');

console.log('시작');

fs.readFile('./readme2.txt',(err,data) => {
	if (err){
		throw err;
	}
	console.log('1번', data.toString());
});

fs.readFile('./readme2.txt',(err,data) => {
	if (err){
		throw err;
	}
	console.log('2번', data.toString());
});

fs.readFile('./readme2.txt',(err,data) => {
	if (err){
		throw err;
	}
	console.log('3번', data.toString());
});

console.log('끝');

콘솔

시작

2번

3번

1번

앞서 배운 지식을 토대로 순서 결과가 시작, 끝, (1,2,3) 이정도는 예상이 가능할텐데

2,1,3이 지 멋대로 나오는데 정상이다 이는 반복할 때 마다 결과가 계속 바뀔 것이다

그래서 이 같이 콜백의 처리 순서 보장을 위해 사용하는 여러가지 방법들이 있다

아예 비동기적인 메서드가 아닌 동기적인 메서드를 사용하기도 하고

아니면 흔히 콜백지옥이라고 부르는 아래의 방법을 하기도 한다

  • 콜백지옥
const fs = require('fs');

console.log('시작');

fs.readFile('./readme2.txt',(err,data) => {
	if (err){
		throw err;
	}
	console.log('1번', data.toString());
	fs.readFile('./readme2.txt',(err,data) => {
		if (err){
			throw err;
		}
		console.log('2번', data.toString());
		fs.readFile('./readme2.txt',(err,data) => {
			if (err){
				throw err;
			}
			console.log('3번', data.toString());
		});
	});
});

console.log('끝');

호출스택에 있는 readfile이 파일요청을 백그라운드에 보내고

백그라운드에서 태스크 큐로 콜백이 넘어가고

콜백이 호출스택에 쌓이는데 호출스택에 쌓인 함수를 실행하니

다시 위와 같은 과정을 반복하는 것이다 물론 콜백 순서는 보장될지 언정 가독성과

코드가 너무 꼬여서 수정시 굉장히 어렵다

그래서 우리는 promise를 통해서 이를 해결한다

profile
세상에 선한 영향력을 끼치는 서비스를 개발하고 싶은 개발자입니다

0개의 댓글