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 연산 작업)에 대해서는 멀티스레드를 사용.
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 또한 마찬가지)
-코드의 흐름과 실제 제어 흐름이 동일
-싱글 스레드 환경에서 메인 스레드를 긴 시간 점유하면 프로그램을 멈추게 함
-자바스크립트 엔진은 하나의 메인 스레드로 구성, 코드를 읽어 한 줄씩 실행.
-브라우저 환경에서는 유저 이벤트를 처리하고 화면을 그린다.
-setTimeout이 실행될 경우 비동기 API에서 작동해서 Queue에 저장. 즉 Js 엔진이 아닌 별도의 환경에서 동작. 지정된 시간이 되면 이를 Task Queue에 보내면 메인 스레드는 현재 실행중인 동작이 없을 때 결과 출력.
-JAVA의 경우 하나의 스레드를 더 사용하여 동작대기를 시키고 시간이 됐을 때 결과 출력.
-현재 실행 중인 코드가 종료되기 전에 다음 라인의 코드를 실행
-프로미스, 콜백 함수를 호출하는 함수 등
-코드 흐름과 실제 제어 흐름 다름
-비동기 작업을 기다리는 동안 메인 스레드는 다른 작업을 처리
-Js 엔진은 비동기 처리를 제공하지 않는다.
-그러나 비동기 코드는 정해진 함수(API)를 제공하여 활용할 수 있다.
-비동기 API : setTimeout, XMLHttpRequest, fetch 등의 WebAPI가 존재.
-node.js의 경우 파일 처리 API, 암호화 API 등을 제공.
-메인 스레드가 Call stack을 이용해서 코드를 읽고 실행
-코드를 읽던 중 비동기 코드 호출 시 비동기 환경에서 Task queue와 Job queue로 코드를 처리하고 결과 함수를 반환함.
-비동기 코드 처리 모듈은 Js 엔진 외부에 있다
-이벤트 루프, 태스크 큐, 잡 큐등으로 구성
-API 모듈은 비동기 요청을 처리 후 태스크 큐에 콜백 함수를 넣는다
-Js 엔진은 콜 스택이 비워지면 태스크 큐의 콜백 함수를 실행한다
(1) 콜백 패턴의 문제 : 콜백 지옥
-중첩 함수로 인해 길어지는 코드, 매우 안좋은 개발자 경험(DX)을 제공.
(1) Promise 패턴의 문제
-초기 호환성 : Promise가 ECMAScript 표준이 되기전에 만들어진 비동기 함수들
-아직 아쉬운 DX : 연결되는 비동기 함수가 많아질수록 길어지는 then 체인
-Js의 비동기 환경을 처음 접하는 사람들에게는 복잡한 개념
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)