※ 본 카테고리의 내용은 부스트캠프 챌린지 기간동안 학습한 내용을 바탕으로 정리한 내용입니다.

 

목차


    0. 멀티스레드 병렬 처리

     

    출처 : https://www.nextree.co.kr/p7292/

     

    우리는 흔히 멀티 스레드 방식으로 서버가 관리될 것이라고 예상합니다.

    클라이언트들의 명령을 어느 한 곳(접수처)에서 받아서 각 스레드에 넘겨주는 형식으로 진행된다면 충분히 잘 돌아갈 것이라고 쉽게 예측할 수 있죠.

    하지만 이 말은 절반은 맞고 절반은 틀린 말입니다.

    만약 동시접속자수가 어마어마하게 많이 발생하는 경우 스레드가 그만큼 많이 발생하게 되고, 그만큼 메모리도 많이 소모하게 되겠죠.

    또한 스레드가 많아지면서 하나의 프로세스 내에서 동시에 여러 스레드가 공유자원에 접근하는 경우가 발생할 수도 있습니다.

    이런 경우 각 스레드가 예상치 못한 요청에 의해 이상한 데이터를 가지고 올 수 있겠죠?

     


    1. 비동기 처리

     

    출처 : https://www.nextree.co.kr/p7292/

     

    위에서 확인한 것처럼 스레드 방식에는 스레드를 생성하는데 그 수에 한계가 존재할 수 있습니다.

    이런 경우에는 특정 스레드에서 진행하는 일이 끝날 때까지 다음 일이 진행되지 않는 상황이 발생할 수 있습니다.

    서버가 돌아가는 데에는 큰 문제가 없겠지만, 기다리는 요청처리 입장에서는 조금 답답할 수 있겠죠?

     

    이 문제를 비동기 방식으로도 처리할 수 있습니다.

    사진을 보시면 요청처리 1이 끝나기 전에 요청처리 2를 받아서 진행하는 그림이죠?

    실제로 Node.js이벤트를 사용하여 비동기 방식으로 클라이언트의 요청을 처리할 수 있습니다.

     

     

    출처 : https://www.nextree.co.kr/p7292/

     

    물론 이 방법도 무조건 좋은 점만 있는 것은 아닙니다.

    이벤트를 처리하는 이벤트 루프 역시 하나 혹은 여러 개의 스레드로 구성되어있고, 그 안에서 요청을 처리하게 됩니다.

    그래서 이벤트를 호출할 때는 비동기로 처리되지만 이벤트 루프 내에서 처리 작업 자체가 오래 걸리게 된다면 전체 서버 처리에 영향을 줄 수 있겠죠? (뿌려줘야 하는데 이벤트 루프가 이해하지 못해서 느리게 뿌리게 되면 처리시간이 길어지니까!!)

     

    조금 더 쉽게 말하면, 동시에 여러 요청을 하게 되는 경우 동기적 방식은 요청이 쌓이면서 대기시간이 걸리게 되지만, 비동기 방식은 이벤트 루프를 한 번 거쳐서 작업을 수행할 수 있도록 각 스레드로 뿌려주기 때문에 능력 여하에 따라 동시에 처리를 할 수 있습니다.

    하지만 엄청나게 큰 작업이 들어오는 경우 이벤트 루프 안에서 처리하는 데에 긴 시간이 걸릴 수 있습니다.

    이 문제를 해결하기 위해서는 이벤트를 가능한 잘게 쪼개서 병렬로 처리될 수 있도록 만들면 됩니다!

     

    1.0. 동기 방식과 비동기 방식

    node.js 내에서 동기 방식을 비동기 방식으로 전환하는 방법은 어렵지 않습니다.

    바로 비동기 API를 사용하면 되는데요!!

    가장 대표적인 비동기 API인 File System을 예로 들어보겠습니다.

     

    아래 예시를 돌려보면, 동기적 방식의 readdirSync를 사용하면 순차적으로 처리가 되지만, 비동기 방식의 readdir함수를 사용하게 된다면 readdir 작업을 수행하면서 동시에 console.log를 찍어내게 됩니다.

    (많은 함수들이 동기/비동기로 나뉘어있고, 함수명 뒤에 Sync가 붙어있으면 동기, 아니면 비동기인 경우가 많습니다!)

     

    // 동기 방식
    
    var fs = require('fs');
    
    var filenames = fs.readdirSync('.');
    var i;
    for (i = 0; i < filenames.length; i++) {
        console.log(filenames[i]);
    }
    // 비동기 방식
    
    var fs = require('fs');
    
    fs.readdir('.', function (err, filenames){
        var i;
        for (i = 0; i < filenames.length; i++) {
            console.log(filenames[i]);
        }
        console.log('ready');
    });
    
    console.log('can process next job...');

     

    이미 다른 프로그램이 언어에 익숙하다면 동기 방식의 경우 쉽게 이해하실 수 있을 것이라고 생각합니다. (순서대로 파일이름이 출력되겠죠?)

     

    그렇다면 아래 비동기 방식의 예에서는 과연 어떻게 될까요?

    동기 함수인 console.log('can process next job...')이 먼저 출력됩니다. 그러고 나서 fs 내부에 있는 요소들이 하나씩 출력되겠죠!

    이유는 간단합니다. 위에서 설명했다시피 컴퓨터는 fs가 일하는 것을 기다려주지 않고 바로 다음 console.log로 넘어가서 바로 출력을 해 주기 때문이죠.

     

    이런 비동기 시스템이 좋은 부분이 많지만, 간혹 불편한 부분도 있을 수 있습니다.

    또한 비동기 함수를 동기적으로 처리하고 싶을 때가 있을 수 있습니다.

    (특히 입력받을 때... Javascript 입문할 때 가장 먼저 느끼는 벽이 바로 입력의 비동기이기도 합니다. 몸소 느꼈습니다 ㅠ)

     

    이럴 때 사용할 수 있는 것이 바로 Promise/Then, 그리고 async/await입니다.

     


    2. Promise/then, async/await

    먼저 Promise미래에 대한 약속을 뜻합니다.

    앞으로 나 이거 할꺼야~ 라는 뉘앙스를 갖죠.

    이 말은 당장 최종 결과를 반환하는게 아니라 나중에 반환한다는 것으로 받아들일 수도 있습니다.

     

    Promise는 대기(초기 상태) / 이행(연산 성공) / 거부(연산 실패) 로 나뉩니다.

    만약 이행이나 거부가 되었을 경우 그 값은 Promise 형태로 다음(then)으로 전달되게 됩니다.

    잘 되었다면 then, 실패했다면 catch가 되겠죠!!

     

    보다 자세한 내용은 아래 모질라 공식 예제를 보시면 더욱 자세하게 나와있습니다.

     

     

    Promise - JavaScript | MDN

    Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

    developer.mozilla.org

     

    이번엔 asyncawait에 대해서 간략하게 알아보겠습니다.

    async와 await는 기존의 비동기 처리 방식인 콜백 함수와 Promise의 단점을 보완하고 읽기 좋은 코드를 작성할 수 있게 도와주는 기능을 합니다.

     

    그렇다면 직접 사용해보면서 어떻게 달라졌는지, 얼마나 편해졌는지 확인해보도록 하겠습니다.

    async와 await의 기본 문법은 간단합니다.

    함수의 앞에 async라는 예약어를 붙이고, 비동기 처리 코드 앞에 await을 붙이면 됩니다.

    다만 여기서 조심해야 할 점은 비동기 처리 메소드는 반드시 Promise 객체를 반환해야 합니다.

    그래야 await가 우리가 의도한 대로 동작하게 되죠.

     

    아래 코드는 아무것도 사용하지 않은 상태로 비동기 코드를 동기처럼 사용하는 코드입니다.

    function foo(callback1) {
    	callback1(function (callback2) {
    		callback2(function(callback3) {
    			callback3( … );
    		});
    	});
    }

     

    이걸 Promise 형태로 만들면 아래와 같죠.

     

    function fooPromise(data) {
    	return new Promise((resolve, reject) => {
    		setTimeout(() => {
    			console.log("fooPromise");
    			if(data === 1000)
    				reject(new Error("my error"));
    			else
    				resolve(data);
    		}, data);
    	})
    }

     

    위 코드를 이용해서 콜백 지옥을 조금 해결해보면 아래와 같이 표현할 수 있습니다.

     

    fooPromise(3000)
    	.then(function (result1) {
    		return fooPromise(result1);
        })
    	.then(function (result2) {
    		return fooPromise(result2);
    	})
    	.then(function (result3) {
    		return fooPromise(result3);
    	})

     

    그런데 이걸 async와 await으로 표현하면?

    확실히 간단해집니다.

    우리가 async와 await의 사용법을 반드시 알아야 하는 이유이기도 하죠!

     

    async function foo(data) {				
    	let result1 = await fooPromise(data);
    	let result2 = await fooPromise(result1);
    	let result3 = await fooPromise(result2);
    ...
    }

     

    2.0. 참고 문헌

     

    async와 await를 사용하여 비동기 프로그래밍을 쉽게 만들기 - Web 개발 학습하기 | MDN

    Javascript에 대한 최신 추가 사항은 ECMAScript 2017 JavaScript 에디션의 일부인 async functions 그리고 await 키워드는 ECMAScript2017에 추가되었습니다. 이 기능들은 기본적으로 비동기 코드를 쓰고 Promise를

    developer.mozilla.org

     

    Promise - JavaScript | MDN

    Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

    developer.mozilla.org

     

    Node.js에서 async await를 사용해야 하는 이유

    자바스크립트로 소스 코드를 작성하다보면 다음과 같은 일에 직면하게 된다. function foo(callback1) { callback1(function (callback2) { callback2(function(callback3) { callback3( … ); // 계속해서 탭이..

    psyhm.tistory.com

     


     

     

     

    반응형
    • 네이버 블로그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기