TIL 113 - Concurrency vs Parallelism(동시성, 병렬성)

김영현·2024년 7월 30일
0

TIL

목록 보기
123/129

CPU가 작업을 처리할 때

CPU는 매우 빠른 속도로 작업을 처리한다. 예를들어 싱글 코어CPU에 1000개의 작업이 들어온다 하면, 시-분할시스템을 이용하여 밀리세컨드 내 우선순위가 높은 작업들을 왔다갔다하며 작업을 처리한다.

이 때 각 작업들은 동시(같은 시점)에 실행되지 않는다. 컴퓨터에 여러 프로그램을 띄워놓을 수 있는데, 동시에 실행되는게 아니라니 무슨 소릴까? 이건 운영체제나 컴퓨터구조를 알고있다면 쉽게 이해된다.

동시성(Concurrency)

가로로 긴 막대가 시간이라고 했을때 색을 입힌 곡선들은 작업이다. 각 작업들은 일정 시간동안 CPU에서 작동하고, 컨텍스트를 스위칭 한다.

위 방식이 바로 동시성이다.

병렬성(Parallelism)

색이 들어간 막대들이 작업이다. 이 작업들은 동시(같은 시점)에 실행된다. 따라서 병렬적으로 실행된다 볼 수 있다.
싱글코어 CPU에서는 불가능하고, 멀티코어 CPU에서는 가능하다.


JavaScript에서의 동시성과 병렬성

하드웨어쪽에서 동시성, 병렬성이 어떻게 굴러가는지 알게되었다. 그렇다면 소프트웨어인 Javascript에서는 동시성과 병렬성을 어떻게 다룰 수 있을까? 이 언어는 싱글스레드로 동작하는데 말이다.

(전통적인 의미의 CPU작업의 동시성, 병렬성이 아닙니다!)

JS에서 동시성 다루기 1) Event loop

엔진이 코드를 한 줄 한줄 읽어 나가는 것도 어떻게 보면 동시성이라 할 수 있다. 그러나 동시성의 개념을 잘 활용했다고 하려면, 개발자가 의도한 대로 각 작업이 교대로 이루어져야한다.

console.log('first');
setTimeout(() => console.log('second'), 3000);
console.log('third');

위 코드의 실행 결과는 모두 알다시피 first => third => second순이다.
이는 setTimeout()과 같은 비동기 함수를 브라우저 내부 이벤트 루프에서 처리해주기 때문이다.
만약 이벤트 루프 없이 싱글 스레드로 동작했다면, second를 출력하는 부분에서 약 3초간 정지했을 것이다.
이는 각 작업이 끊기지 않고 적절히 전환하는 동시성을 이용한 프로그래밍 방식이다.

async/await을 활용한 예제도 한번 알아보자.

모두 알다시피 generator와 Promise를 결합한 비동기 처리를 간편하게 사용할 수 있게해주는 문법이다.

const wait = (ms = 3000) => new Promise((resolve) => setTimeout(() => {
	resolve();
  	console.log('second')
},ms));

const getData = async() => {
  	console.log('first');
	wait();
	console.log('third');  	
}

console.log('start');
getData();
console.log('end')

당연하게도 위 예제를 실행하면 아래와 같은 결과가 나온다.

동시성을 활용하지 못했다면, 싱글스레드인 JS는 wait()함수를 마주했을때 3초간 block될 것이다.
그러나 Event loop를 활용해서 동시성 프로그래밍을 잘 활용할수 있게 되었다.

여기서 헷갈리는점은 브라우저에서 병렬적으로 fetching등을 해주는데, 왜 동시성이라고 표현할까?
=> 관점을 브라우저가 아닌 JS에 한해서 바라본다. 각 코드(작업)이 번갈아가면서 진행됨. === 동시성

JS에서 동시성 다루기 2) Generator

제너레이터를 모른다면 TIL - 88에서 보실수 있습니다!

function* countUpTo(max) {
  for (let i = 1; i <= max; i++) {
    yield i;
  }
}

const counter = countUpTo(3);
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3
console.log(counter.next().value); // undefined

함수의 제어권을 외부에 양도하여 동시성을 개발자가 편하게 관리할 수 있게 해준다.

이제 JS에서 병렬성을 다루는 법에 대하여 알아보자!

JS에서 병렬성 다루기 1) Promise.all

참고로 이 함수는 TIL - 87에서 바닐라 JS로 구현한 적이 있습니다!

Promise.all은 인수로 전달된 작업들이 병렬적으로 실행된다. 물론 관점에 따라 다르다.
Promise.all바깥 코드에서 바라보면, 다른 작업들이 blocking되지 않으니 동시성을 다루는 것이다.
Promise.all전달된 코드에서 바라보면, 동시에 실행되니 병렬성을 다루는 것이다.
항상 관점에 유의하자.

const p1 = Promise.resolve("첫번째 Promise");

const p2 = "두번째 string";

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("3초뒤 resolve되는 Promise");
  }, 3000);
});

const p4 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("5초뒤 resolve되는 Promise");
  }, 5000);
});

console.time('start');

const p = Promise.all([p1, p2, p3,p4])
  .then((values) => {console.log(values); console.timeEnd('start');})
  .catch((err) => console.log(`이행되지 않음 : ${err}`));

만약 Promise.all()에 전달된 인수들이 병렬적으로 실행되지 않는다면, 총 약 8초가 소요될 것이다.

그러나 전달된 인수중 제일 긴 시간인 5초만 소요된 것을 볼 수 있다. 이는 전달된 인수들이 병렬적으로 실행됨을 의미한다.

JS에서 병렬성 다루기 2) Web Workers

Web Worker란, 주 실행스레드와 분리된 별도의 백그라운드 스레드를 생성할 수 있는 기능이다.
백그라운드 스레드에서 무거운 작업을 처리하게 하면 병렬처리가 가능해진다.
아직 사용해본 적이 없어서 정확한 설명이 불가능하기에 이런 기능이 있다는 것만 알아가겠다!(다음편에서 다뤄볼게요)


결론

동시성과 병렬성의 차이를 알게되었다. 뿐만 아니라, 관점의 차이에 따라 동시성과 병렬성이 나뉜다는 것도 깨닫게 되었다.
예전에 동기,비동기,블로킹,논 블로킹에 대해 데브코스 멘토님께 여쭤본 적이 있었다.
그때 어디에 포커싱 하느냐의 차이라고 하셨었는데 당시에는 백프로 이해하지 못하는 개념이었다. 하지만 개발을 계속 하며 지식을 습득하니 당시 해주셨던 이야기가 얼마나 값진 건지 깨닫게 되었다.
천천히 지식을 쌓아나가자!

profile
모르는 것을 모른다고 하기

0개의 댓글