자바 스크립트 심화

김종현·2023년 3월 28일
0

Js

목록 보기
3/13

싱글 스레드 vs 멀티 스레드

1) 싱글 스레드
-경쟁 상태 Race Condition, 교착 상태 Deadlock X
-하지만 비동기 작업을 동반하는 JS의 경우는 위험이 있다
-멀티스레드 대비 코드 복잡도가 낮음

-그러나 병렬처리보다는 작업 처리 효율이 평균적으로 떨어짐.

2) 멀티 스레드
-평균적으로 싱글스레드보다 작업 처리 효율이 높음

-그러나 사용시 경쟁 상태, 교착 상태를 고려해야함 : 코드 복잡도가 높음

2-1) 경쟁 상태
-하나의 리소스를 두고 경쟁하는 상태.
-한 사람이 작업 중일 경우 다른 사람이 작업을 할 수 없도록 해야 함 : lock(혹은 mutex) 걸어야 함.

// 계좌 잔고
let globalBalance = 100; // 잔고 100원

// 딜레이 함수, DB에서 계좌 정보를 가져오는 시간을 시뮬레이션화한 함수 0~100ms
const delay = () =>
  new Promise((resolve) => setTimeout(resolve, Math.random() * 100));

async function loadBalance() {
  await delay(); // DB에서 정보를 가져올 땐 시간이 소요되므로 delay함수로 해당 지연을 가장
  return globalBalance;
}

async function saveBalance(value) {
  await delay(); // DB에 정보를 저장할 땐 시간이 소요되므로 delay함수로 해당 지연을 가장
  globalBalance = value;
}

// 아래 작업은 atomic하게 처리되어야 한다....
async function withdraw(amount) {
  const balance = await loadBalance();
  console.log(`인출 가능한 금액은 ${balance}원입니다.`);
  const newBalance = balance - amount;
  if (newBalance < 0) {
    console.log("인출 가능한 금액을 초과한 금액입니다. 다시 시도해주세요.");
    return;
  }
  await saveBalance(newBalance);
  console.log(`인출 완료입니다. 현재 계좌 잔고는 ${newBalance}원입니다.`);
}

// 아래 작업은 atomic하게 처리되어야 한다....
async function deposit(amount) {
  const balance = await loadBalance();
  console.log(`현재 계좌 잔고는 ${balance}원입니다.`);
  const newBalance = balance + amount;
  await saveBalance(newBalance);
  console.log(`입금 완료입니다. 현재 계좌 잔고는 ${newBalance}원입니다.`);
}

async function main() {
  // await 없음. 여기선 Promise가 fulfilled되기 기다리지 않는다는 뜻
  const transaction1 = deposit(100);
  // await 없음. 여기선 Promise가 fulfilled되기 기다리지 않는다는 뜻. 위의 transaction1과 동시에 실행이 됨. Promise.all([transaction1, transaction2])와 같은 효과
  const transaction2 = withdraw(100);
  // 위의 두 transaction들은 하나가 완전히 완료된 후 다른 하나를 실행한 것이 아니기 때문에 race condition이 발생함.
  await transaction1;
  await transaction2;
  const balance = await loadBalance();
  console.log(`최종 계좌 잔고: ${balance}`);
}

main();
//main함수는 await 해주지 않아 매번 결과 값이 다르게 나옴.

2-2)교착 상태 : Dead Lock

-서로가 리소스를 점유하고 내놓지 않는 것.

블로킹 & 논 블로킹

1)블로킹
-하나의 작업 마치고 다음 작업을 순차적으로 실행
-작업이 길어지면 다음 작업이 지연
-Js에선 동기(synchronous)라는 용어와 혼용

function waitFiveSeconds() {
  const startTime = Date.now();
  // 5초간 아무 것도 못하고 대기
  while (Date.now() - startTime < 5000) {}
  console.log("5초 후!");
}

function sayHello() {
  console.log("Hello, World!");
}

waitFiveSeconds();
sayHello();

2)논 블로킹
-Js에서는 비동기라는 용어와 혼용
-보통 결과값을 return으로 바로 받지 못하며 CB함수를 통해 받는 경우가 많다.(단, Promise의 등장으로 콜백함수가 아닌 다른 방식으로 받게 된다.)
-기술적으로 논블로킹 방식은 싱글스레드만으로는 불가

// 비동기 함수
function waitFiveSeconds() {
  // 5초가 지나는 사이에도 다른 작업을 할 수 있도록 해줌
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("5초 후!");
      resolve();
    }, 5000);
  });
}

function sayHello() {
  console.log("Hello, World!");
}

// 5초 동안 대기하면서
waitFiveSeconds();
// 곧바로 인사도 가능하다
sayHello();

3)Js가 싱글스레드인데 동시 작업이 가능한 이유
-setTImeout : 몇 초 후에 특정 작업을 수행 시키며 해당 시간이 경과되기까지 별도의 작업 가능
-fetch : 원격 서버에 있는 데이터를 요청, 응답이 오기 전에 다른 작업 수행 가능
-마치 스레드가 하나이지 않은 것처럼 작동
-이벤트 루프의 도움을 받는다.
-싱글스레드로 순차적으로 실행시키되 특정 비동기 작업(IO 작업 또는 CPU 연산 작업)에 대해서는 멀티스레드를 사용.

자바스크립트 실행 환경 : Runtime

1)Js 실행 환경 조감도
-자바스크립트 코드를 실행하기 위한 환경의 구성 요소들
-Js 엔진, 이벤트 루프+큐, 플랫폼 지원 API

1-1) Js 엔진
-코드를 읽어 해석하고 작업 수행, 엔진 자체는 작업만 수행할 뿐 비동기/동기와는 관계 없음.
-V8, SpiderMonkey, JavaScriptCore

1-2) 이벤트 루프 + 큐
-Js가 비동기로 실행되도록 하는 역할

1-2-2) 이벤트 루프
-비동기 작업을 마친 후 실행될 콜백함수가 쌓이는 곳, 큐에 쌓인 콜백함수를 꺼내 Js 엔진에 전달.
-Task Queue(Marcro Task Queue), Micro Task Queue
-엔진은 전달받은 함수들을 받은 순서대로 실행

1-3) 플랫폼 지원 API
-비동기/동기 작업들의 묶음
-비동기 작업 실행은 대부분 플랫폼 API를 통해 일어난다
-자바스크립트는 플랫폼 API를 사용해서 비동기 작업을 수행㏖위임㏗하며 작업이 마쳤을 때 실행되는 콜백함수를 대부분 해당 API 매개변수로 전달한다(비동기 API의 경우, Promise, async/await의 경우 제외)
-작업이 완료되면 큐에 콜백함수를 등록한다(비동기 API의 경우)
-브라우저의 web API, Node.js API
-ex) setTimeout, setInterval, fetch, console, fs, path

이벤트 루프

동기적 제어 흐름

-현재 실행 중인 코드가 종료되기 전까지 다음 코드 실행x
-분기문(ex if문), 반복문, 함수 호출 등이 동기적으로 실행.(Js 또한 마찬가지)
-코드의 흐름과 실제 제어 흐름이 동일
-싱글 스레드 환경에서 메인 스레드를 긴 시간 점유하면 프로그램을 멈추게 함

  • Js는 무한 루프 for문에 걸리면 프로그램을 멈추게 하지만 타 멀티 스레드 프로그램은 멈추지 않음.
  • 따라서 동기와 비동기를 구분해 실행이 오래 걸리는 것은 비동기적으로 작성해야 함.

    -동기적 제어 흐름 예시
  • let a = 10 같은 선언문
  • console.log 같은 동기적 실행
  • for 같은 반복문
  • 함수 실행

자바스크립트에서 비동기 작업의 수행

-자바스크립트 엔진은 하나의 메인 스레드로 구성, 코드를 읽어 한 줄씩 실행.
-브라우저 환경에서는 유저 이벤트를 처리하고 화면을 그린다.

-setTimeout이 실행될 경우 비동기 API에서 작동해서 Queue에 저장. 즉 Js 엔진이 아닌 별도의 환경에서 동작. 지정된 시간이 되면 이를 Task Queue에 보내면 메인 스레드는 현재 실행중인 동작이 없을 때 결과 출력.

-JAVA의 경우 하나의 스레드를 더 사용하여 동작대기를 시키고 시간이 됐을 때 결과 출력.

-현재 실행 중인 코드가 종료되기 전에 다음 라인의 코드를 실행
-프로미스, 콜백 함수를 호출하는 함수 등
-코드 흐름과 실제 제어 흐름 다름
-비동기 작업을 기다리는 동안 메인 스레드는 다른 작업을 처리

-Js 엔진은 비동기 처리를 제공하지 않는다.
-그러나 비동기 코드는 정해진 함수(API)를 제공하여 활용할 수 있다.
-비동기 API : setTimeout, XMLHttpRequest, fetch 등의 WebAPI가 존재.
-node.js의 경우 파일 처리 API, 암호화 API 등을 제공.

-메인 스레드가 Call stack을 이용해서 코드를 읽고 실행
-코드를 읽던 중 비동기 코드 호출 시 비동기 환경에서 Task queue와 Job queue로 코드를 처리하고 결과 함수를 반환함.

  • setTimeout의 경우 Web API 모듈에서 지정 시간 뒤 setTimeout이 종료되면 Task queue에서 처리하게 됨.
    -Job queue 경우 프로미스 API나 애니메이션 프레임 등에 사용.
    -비동기 코드가 끝났을 때 메인 스레드가 콜 스택을 다 비운 상황에서 이벤트 루프를 체크하면서 Task Queue에 Task가 남아있을 경우 이벤트 루프를 통해서 콜 스택에 넣고 코드를 실행. 이후 콜 스택이 비워지면 반복.

비동기 처리 모델

-비동기 코드 처리 모듈은 Js 엔진 외부에 있다
-이벤트 루프, 태스크 큐, 잡 큐등으로 구성
-API 모듈은 비동기 요청을 처리 후 태스크 큐에 콜백 함수를 넣는다
-Js 엔진은 콜 스택이 비워지면 태스크 큐의 콜백 함수를 실행한다

  1. 전통 : 콜백 패턴
    -비동기 작업은 return이 아닌 별도의 결과를 반환하는 방식이 필요했고 CB 패턴이 그 대안.
    -Js는 함수를 일급 객체로 취급했기에 가능.
  • 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다. 보통 함수에 인자로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 한다.(함수도 일급 객체)

(1) 콜백 패턴의 문제 : 콜백 지옥
-중첩 함수로 인해 길어지는 코드, 매우 안좋은 개발자 경험(DX)을 제공.

  1. 개혁 : 프로미스 패턴
    -콜백 지옥의 해결.
    -then() : 로직 상 순차적으로 호출되어야하는 비동기 함수들을 쉽게 연결해줌
    -catch() : 비동기 함수에서 에러 발생시 에러 처리를 쉽게 도와줌
    -then, catch, finally : 비동기 함수를 일관성 있는 형식으로 관리.
    -DX 향상 : 코드 가독성, 체이닝 기법
    -기타 비동기 함수의 기능을 추가 : Promise.all/allSettled/any/race

(1) Promise 패턴의 문제
-초기 호환성 : Promise가 ECMAScript 표준이 되기전에 만들어진 비동기 함수들
-아직 아쉬운 DX : 연결되는 비동기 함수가 많아질수록 길어지는 then 체인
-Js의 비동기 환경을 처음 접하는 사람들에게는 복잡한 개념

  1. 평정 : Async/await 패턴(문법)
    -비동기 코드를 동기 코드와 같이 보게 함
    -가독성 향상, 흐름 파악 용이, 익숙한 문법, 익숙하지 않은 사람들도 읽기 쉬움
    -높은 호환성 : Promise로 되어있는 비동기 함수들을 async/await만 붙여서 손쉽게 코드를 치환
async function main(){
try {
const response = await fetch(`/data/sample.json`);
const samples = await response.json㏖㏗;
for (let i = 0; i < samples.length; i++){
/* do something */
 }
} catch (error){
console.log(error.message);
 }
}

정리

-Js는 싱글스레드지만 비동기 실행 방식으로 부족한 성능을 극복
-Js는 논블로킹 방식을 지원해 일정 수준의 동시성을 보여줌
-Js의 실행 환경(Runtime)의 구조와 각 구성요소의 역할이 있음
-Js의 비동기 작업 수행 방식 : cb, promise, async/await
-오늘날의 자바스크립트는 멀티스레드를 지원함(web worker OR worker thread)

profile
나는 나의 섬이다.

0개의 댓글