본문 바로가기

[JavaScript] 정말 Promise.all()은 병렬로 실행될까?

by mugglim 2022. 6. 1.

병렬성과 트랜잭션

동시성(Concurrency)과 병렬성(Parallelism)

프로그래밍을 공부하다 보면 동시성병렬성이란 용어를 듣게 된다. 두 용어 모두 멀티 태스크를 처리하는 방법이다.

우선 동시성을 알아보자. CPU는 한 시점에 하나의 태스크 밖에 처리하지 못한다. 만약 CPU가 하나 인데 여러 태스크를 처리해야 한다면, 태스크를 번갈아가면서 실행시켜야 한다. 이를 동시성이라고 말할 수 있다. 운영체제에서의 컨텍스트 스위칭을 생각해보자.

병럴성은 CPU가 2개 이상인 경우에 사용되는 개념이다. CPU가 여러개 이다 보니, 한 시점에서 여러 태스트를 처리할 수 있다.

(동시성과 병렬성을 더 자세히 알고 싶다면, Deview2021 심흥운님의 세션을 들어보면 좋을 것 같다.)

트랜잭션(Transaction)

트랜잭션이란 용어는 데이터베이스에서 나오는 개념이다. 여러 작업을 하나의 논리적인 단위로 묶은 개념이다. 트랜잭션이 사용되는 이유는 데이터의 일관성과 관련있다. 

만약 트랜잭션에서의 작업이 하나라도 실패한다면, 데이터의 일관성을 위해 트랜잭션을 시작하기 전의 시점으로 롤백해야 한다. 만약, 모든 작업이 성공했다면 작업의 결과를 데이터를 반영하는 커밋을 해야 한다.

(트랜잭션도 더 자세히 알고싶다면, 최범균님의 강의를 들어보자.)

JavaScript의 비동기 로직

JavaScript는 싱글 스레드

JavaScript는 싱글 스레드 기반의 언어이다. 싱글 스레드 기반의 시스템에서 병렬적으로 태스크를 처리할 수 없다. 그래서 JavaScript는 비동기 로직을 이벤트 루프와 Node.js의 백그라운드에 도움을 받아 병렬적으로 처리한다.

Promise.all()

Promise.all()은 Promise 로직을 병렬로 처리하는 메소드이다. 단, JavaScript가 병렬로 처리하는 것이 아니다.

async 함수를 사용한다고 가정해보자. async 함수에서 await를 사용하면 Promise 작업이 resolve 될 때 까지 blocking 된다. 이와 달리, Promise.all()은 각 Promise가 처리될 때 blocking 처리하지 않는다. 

코드를 통해 확인해보자.

const fetch1 = () => new Promise(res => { setTimeout(() => res(1), 1000)})
const fetch2 = () => new Promise(res => { setTimeout(() => res(2), 2000)})
const fetch3 = () => new Promise(res => { setTimeout(() => res(3), 3000)})

const pipe1 = async () => {
  console.time("pipe1");
  await fetch1();                    // blocking! ... wait  
  await fetch2();                    // blocking! ... wait  
  await fetch3();                    // blocking! ... wait  
  console.timeEnd("pipe1");         // pipe1 ~= 6000ms  
}

const pipe2 = async () => {
  console.time("pipe2");
  await Promise.all([fetch1(), fetch2(), fetch3()]);  // blocking ... wait
  console.timeEnd("pipe2");                           // pipe2 ~= 3000ms
}

pipe1() 함수의 처리 시간은 약 6000ms이며, pipe2() 함수는 약 3000ms로 측정된다. 처리 시간을 통해 Promise.all()이 병렬로 처리되는 것을 확인할 수 있다.

언제 Promise.all()을 사용하는게 좋을까?

Promise.all()을 사용하는 경우는 크게 2가지라고 생각한다.

  1. Promise 작업 간 순서가 중요하지 않고, 병렬로 Promise 작업을 처리하고 싶은 경우
  2. 여러 Promise 작업을 하나의 트랜잭션으로 처리하고 싶은 경우. Promise.all()은 하나의 Promise라도 reject(실패) 하면, catch() 분기문으로 넘어간다.

Ref.

댓글