본문 바로가기

CS 학습 정리

비동기 문법과 병렬처리, Node.js의 병렬처리 방식에 대해

728x90

Javascript의 비동기 문법 정리

  • 자바스크립트는 싱글 스레드 매커니즘을 사용하는 언어이기 때문에 모든 명령은 순차적으로 작동한다. 이것을 동기라고 부른다.
  • 동기 방식은 간단하고 직관적이지만 작업이 오래 걸리거나 응답이 늦어지는 경우엔 프로그램 실행 중간에 붕 뜨는 시간이 발생한다. 따라서 자바스크립트는 비동기(Asynchronous)라는 개념을 도입하여 다른 동작을 동시에 수행할 수 있도록 하였다.

자바스크립트는 싱글 스레드인데 어떻게 작업을 동시에 처리하지?

  • 자바스크립트는 크게 힙, 콜스택, 이벤트루프, Web API, Callback Queue로 이뤄진다. 자바스크립트를 직접 실행하는 콜스택은 싱글스레드지만 서버에게 리소스를 요청하거나 파일 입출력 혹은 타이머 대기 작업을 실행하는 Web Apis는 멀티스레드이기 때문에 비동기 작업이 가능하다.

Web API는 타이머, 네트워크 요청, 파일 입출력, 이벤트 처리 등 브라우저에서 제공하는 다양한 API를 포괄하는 총칭이다. 이 말은 즉 자바스크립트 자체의 기능이 아니라는 의미다. 만약 브라우저에서 멀티스레드를 제공하지 않는다면 사용할 수 없다.

자바스크립트 멀티 스레딩 도구

  1. Web Worker : 브라우저에서 사용할 때 멀티스레딩을 지원한다.
  2. Worker_thread : Node.js에서 사용할 때 멀티스레딩을 지원한다.

스레드 풀

  • Node.js에서 스레드풀은 I/O 작업을 처리하는 데 사용되는 스레드 집합이다. 노드는 내부적으로 스레드풀을 사용해 입출력 작업을 관리한다.

  • 기본적으로 4개의 스레드를 사용하며, 각 스레드는 작업이 완료되면 콜백 함수를 호출하여 결과를 반환한다. 이렇게 함으로써 노드는 비동기적으로 작업을 처리하면서도 블로킹되지 않고 다른 작업을 처리할 수 있다.

  • 스레드 풀은 기본적으로 libuv 라이브러리에서 관리한다.

    uv_work_t 유형 작업 요청 유형.

    typedef void ( * uv_work_cb ) ( uv_work_t * req )

    uv_queue_work() 스레드 풀에서 실행될 콜백을 전달합니다.

    typedef void ( * uv_after_work_cb ) ( uv_work_t * req , int status )

    uv_queue_work() 스레드풀에서 작업이 완료된 후 루프 스레드에서 호출될 콜백이 전달됩니다. 작업이 상태를 사용하여 취소된 경우 uv_cancel() UV_ECANCELED


    -- libuv documentation

  • 스레드 풀은 export UV_THREADPOOL_SIZE 명령어로 개수를 변경할 수 있으며
    Changed in version 1.30.0: the maximum UV_THREADPOOL_SIZE allowed was increased from 128 to 1024. 1024개까지 할당할 수 있다. 개인의 CPU사양에 따라 적절한 량을 찾아야한다.

  • 레퍼런스

  • https://inpa.tistory.com/entry/%F0%9F%8C%90-js-async

  • https://docs.libuv.org/en/v1.x/

  • https://velog.io/@milkcoke/Node.js-Thread-Pool

    병렬처리와 쓰레드

    위에서 자바스크립트는 결국 병렬처리가 안되는 것을 알아보았다. 그렇다면 병렬처리란 무엇일까

    병렬처리 : N개의 프로세스를 이용하여 개발하는 것

    멀티 스레드 : 프로세스의 가장 작은 작업 단위가 스레드이다. 즉 , 멀티스레드는 하나의 프로세스를 병렬처리하는 녀석

    즉, 멀티스레드는 병렬처리를 구현하는 하나의 방법이며 병렬처리는 GPU를 사용한 CUDA프로그래밍, 멀티스레딩 등 많은 방법이 존재한다. 자바스크립트에선 이것을 비동기로 처리하는데 사실은 병렬이 아닌 눈속임이되는 것이다.

Event Handler

이벤트 핸들러는 어떠한 이벤트가 생기면 호출되는 함수이다. 브라우저의 클릭이 대표적이다.

  • 브라우저에서 어떤 클릭이 되었을 때, 즉 html에서 어떤 동작을 감지에 javascript를 실행할 수 있다.
  • Node.js는 콘솔환경이므로 이러한 핸들러를 사용하지 않는다. 대신 밀접하게 행동하는 Event Emmiter를 활용하게 된다.

Promise

  • 프로미스 나인 는 비동기 함수라 원래는 사용할 수 없는 것을 동기처럼 사용할 수 있는 상태를 말한다. 프로미스는 비동기 연산이 종료된 후 호출될 함수를 지정할 수 있다. 프로미스는 마치 동기객체 처럼 사용할 수 있는데, 미래시점에 어떤 결과를 제공하겠다는 "약속"을 제공한다.
  • 프로미스는 다음과 같은 상태를 가진다.
    • 이행하지도 거부하지도 않은 대기상태 (pending)
    • 연산이 성공적으로 이루어진 완료상태 (fulfiled)
    • 실행이 실패한 거부상태 (reject)
      예제
      const promiseEx = new Promise((resolve, reject) => {
      resolve("A")
      });
      

function print(A){
console.log(A);
}

function print2(){
console.log("실패")
}

console.log("이전")
promiseEx.then(print, print2);
console.log("이후")


  결과 : 이전 이후 A
  <br> 위 함수는 프로미스지만 지연될 이유가 없는 동기함수와 같은 시간이 걸릴탠데도 이전 이후가 먼저 나왔다. 여기서 우리는 비동기 함수가 눈속임이라는 것을 다시 알 수 있다.


### Event Emmiter
이벤트 에미터의 동작들도 모두 프로미스처럼 비동기로 동작한다. 둘은 사용하는 목적이 다르기 때문에 적절하게 선택하면 된다. 어떤 동작이 끝났을 때 함수를 콜백하고 싶으면 프로미스, 어떤 이벤트가 발생했을 때 특별한 파라미터가 들어왔을 때 모든 리스너에게 요청할 수 있다.

 ``` javascript
  const Emmiter = require('events');

class customEventEmmiter extends Emmiter {}

const emmiter = new customEventEmmiter();

emmiter.on('event1', (param1) => {
    console.log("이벤트 1 발생 " + param1);
})


emmiter.on('event2', (param1, param2) => {
    console.log("이벤트 2 발생 " + param1 + param2);
})

emmiter.on('event1', (param1) => {
    console.log("이벤트 1_2 발생 " + param1);
})


emmiter.emit('event1', "A");

결과 : 이벤트 1 발생 A
이벤트 1_2 발생 A

두개의 반응이 출력된다.