NodeJS

NodeJS, 블로킹(Blocking)과 넌블로킹(Non-Blocking)

iKay 2019. 1. 20. 20:02
반응형

NodeJS에서 블로킹(Blocking)과 넌블로킹(Non-Blocking) 호출(call)을 할 때 차이점[각주:1]을 살펴보자. 




블로킹(Blocking)과 넌블로킹(Non-Blocking)


어떤 task가 블로킹, 넌블로킹인지를 따지는 것은 쉽지 않은 주제라고 생각한다. 관점에 따라 다를 수 있기 때문이다. 


나는 블로킹(Blocking)이란, 현재 컨텍스트에서 다음의 Javascript 코드가 수행되기 위해 현재의 Javascript 연산, 혹은 non-Javascript 연산이 끝날 때 까지 기다려야 하는 상황이라고 생각한다. 다시 말해, 블로킹은 이벤트 루프(event loop)[각주:2]가 계속해서 다음의 Javascript 코드를 수행할 수 없게 되는 현상이라고 본다. 반면, 넌블로킹은 다음의 Javascipt 코드가 수행되기 위해 현재의 Javascript연산 혹은 non-Javascript 연산이 끝나는 것을 기다지리 않아도 되는 상황이다. 이 둘의 차이를 이해는 것은 싱글 쓰레드 이벤트 루프 방식인 NodeJS를 더 잘 다루기 위해 매우 중요하므로 잘 이해하고 넘어가야 한다고 본다.     


I/O[각주:3] 작업과 같이 non-Javascript 연산이 Sync로 끝나는 것을 기다는 것은 블로킹이라 볼 수 있을 것이다. 하지만 Javascript 연산이 CPU를 많이 사용해서 성능이 저하되는 것을 반드시 블로킹이라고 볼 수는 없다[각주:4]. 이 부분이 애매하다. 전체 task로 본다면 블로킹이라 볼 수 있겠지만 블로킹이 되는 함수 stack 내에서 task가 이어서 실행되므로 반드시 블로킹이라고 볼 수는 없는 것이다. 


일반적으로 libuv[각주:5]를 사용하는 NodeJS 표준 라이브러리(NodeJS standard library)가 동기적(Synchronous) 메쏘드(method)라면 블로킹 연산을 한다. Native module도 마찬가지다. NodeJs 표준 라이브러리에서 I/O와 관련된 메쏘드를 호출 할 때 넌블로킹, 콜백을 받는 방식인 비동기적(asynchronous) 방식으로 사용할 수 있다. 그러나  몇몇 메쏘드는 이름 뒤에 Sync 로 끝나는 블로킹 함수도 있다.          



   

코드로 보자 



위에서 언급한대로, 블로킹 메쏘드는 동기적으로, 논블로킹 메쏘도는 비동기적으로 수행된다[각주:6]


파일 시스템(File System) 모듈을 사용하는 예를 들어보자. 파일을 읽을 때 동기적 코드는 다음과 같다. 


const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read


그리고 비동기적 코드는 다음과 같다. 


const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
});


첫 번째 예제가 두 번째 예제보다 코드가 간결하지만, 첫 번째 예제는 2번 라인에서 블로킹이 일어난다. 그래서 파일을 전부 읽기 전 까지 다른 Javascript가 수행될 수 없다. 참고로 동기적 메쏘드 형태에서 에러가 발생했을 때(an error is thrown), 이를 반드시 캐치(catch) 해줘하며 그렇지 않으면 프로세스가 크러쉬하게 된다. 반면에 비동기적 메쏘드 형태에서는 위에서 보여진대로 에러를 throw할지를 프로그래머가 결정하면 된다.     


위 예제의 코드를 조금만 더 확장해서 보자. 


동기방식은, 


const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read
console.log(data);
// moreWork(); will run after console.log


비동기 방식은,


const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
// moreWork(); will run before console.log

  

이다. 첫 번째 예제에서 console.log는 moreWork( ) 가 수행되기 전에 실행된다. 반면 두 번째 예제에서는 console.log가 fs.readFile( )이 넌블로킹이기 때문에 Javascript가 블로킹되지 않고 계속 수행될 수 있다. 그래서 moreWork( ) 가 수행된 후 console.log가 실행된다. 두 번째 방식처럼 파일을 읽는 것이 끝나는 것을 기다리지 않고 moreWork( )를 수행할 수 있는 디자인이 같은 시간에 더 많은 처리를 한다(allow for higher throughput).  




동시성과 처리량(Concurrency and Throughput)


NodeJS에서 Javascript는 싱글 쓰레드(single thread)로 수행된다. 그래서 NodeJS에서 동시성이라 함은 다른 일을 마친 후, Javascript 콜백 함수를 수행하는 이벤트 루프의 수용량 말하는 것이다. 따라서 동시성을 높이기 위해 동시에 실행시킬 코드는 I/O와 같은 non-Javascript 연산이 일어날 때, 이벤트 루프가 계속 실행되도록 해주는 것이 좋다.


예를 들어 웹 서버에 대한 각 요청을 수행하는데 50ms가 걸릴 때 database I/O가 45ms 걸리고, 비동기적으로 처리한다고 생각하보자. 이렇게 넌 블로킹 비동기 연산 방식이기 때문에 45ms 동안 다른 일을 할 수 있게 되고 따라서 그 동안에 다른 요청을 처리할 수 있다. 이 점 때문에 블로킹 메쏘드 대신 넌 블로킹 메쏘드를 사용하게 되면 더 많은 요청을 처리할 수 있다[각주:7]



      

블로킹과 넌 블로킹 코드를 잘 못 이해해서 사용할 때 위험성


 

NodeJS에서 I/O를 다룰 때 아래와 같이 블로킹, 넌 블로킹 코드를 혼돈해서 사용해서 사용하게 되면 의도치 않은 결과나 나오게 된다. 만약 어떤 파일을 읽은 후 지우고 싶을 때를 생각하면서 아래 예제 코드를 보자. 


const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
fs.unlinkSync('/file.md');


위의 예제 코드를 실행시키면, fs.unlinkSync( )가 fs.readFile( )이 실행되지 전에 실행돼 file.md를 읽기 전에 지워버릴 것이다. 따라서 파일을 읽은 후 지우기 위해서는 아래와 같이 코딩해줘야 한다. 

 

const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
if (readFileErr) throw readFileErr;
console.log(data);
fs.unlink('/file.md', (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
});


위와 같이 fs.readFile( ) 콜백 속에 fs.unlink( )가 호출돼야 의도한대로 file.md를 읽고 지울 수 있다.  



  1. https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/ 의 내용을 토대로 읽기 편하게 번역했으니 번역한 내용이 어색하다면 원문을 봐도 좋을 것 같다. [본문으로]
  2. 이벤트 루프(event loop)도 NodeJS에서 중요한 주제 중 하나이다. 이것에 대한 내용도 따로 포스팅할 예정이다. [본문으로]
  3. I/O는 libuv에 의해 시스템에서의 디스크와 네트워크와의상호작용(읽기/쓰기)를 일컫는다. [본문으로]
  4. 따라서 CPU연산이 많아 성능이 나오지 않는다면 다른 쓰레드가 이 일을 처리하도록 하거나 Javascript보다 더 좋은 언어를 사용하도록 고려해야 한다. [본문으로]
  5. libuv에 대한 내용은 이번 포스팅 범위의 내용을 살짝 벗어난다. 궁금하다면 http://libuv.org/ 를 참고하자. [본문으로]
  6. 블로킹은 동기적, 논블로킹은 비동기적이라는 것과는 다른 말이다. 최소한 NodeJS에서는 블로킹 메쏘드는 동기적으로, 논블로킹 메쏘드는 비동기적으로 동작한다는 것을 알고 넘가가자. [본문으로]
  7. 넌 블로킹이 일반적으로 더 좋은 성능을 내지만 코딩방식이 블로킹 방식과 다르므로 처음에는 낯설게 느껴질 수 있다. [본문으로]
반응형