자바스크리브처럼 싱글 스레드인 경우 실행 컨텍스트에 함수가 스택과 같이 싸이고
상위 함수가 종료되어야 다음 함수가 실행이 된다. 여기서 하위 함수는 블로킹(작업중단) 처리된다.
이처럼 현재 실행 중인 태스크가 종료할 때까지 다음 태스크가 대기하는 방식을 동기처리라 한다.
: A가 끝나면 B가 시작한다. (A가 끝난 시간과 B가 시작하는 시간을 맞춘다.)
: 제어권반환과 결과값이 함께 일어난다.
동기와는 다르게 상위 함수가 종료되지 않았음에도 하위 함수가 실행되는 방식을 비동기 처리라 한다.
function foo() {
console.log("foo");
}
function bar() {
console.log("bar");
}
setTimeout(foo, 3000);
bar();
// bar
// foo (3초 후 반환)
위 예제 처럼 상위함수 foo()가 종료되기 전에 bar()함수값이 먼저 반환되는 경우를 비동기 처리라 한다.
: A의 끝과 상관없이 B가 실행 될 수 있다.
: 결과값이 없는데 제어권이 반환 될 수 있다.
즉, 여러 일이 동시에 발생할 수 있다.
한 함수의 실행 흐름을 막지(block)않고, 걔속 실행한다.
그리고 그 함수가 끝났을 때, 프로그램은 실행결과에 접근한다.
비동기 함수는 전통적으로 콜백 패턴을 사용하여 만든다.
그리고 타이머 함수인 setTimeout과 senInterval, HTTP요청, 이벤트 핸들러는 비동기 처리 방식으로 동작한다.
앞서 설명한대로 자바스크립트는 싱글 스레드로 한번에 하나의 태스크만 처리한다.
하지만 브라우저를 살펴보면 태스크가 동시에 처리되는 것처럼 느껴진다.
이는 자바스크립트의 동시성을 지원하는 이벤트 루프와 관계가 있다.
즉, 비동기로 동작하는 setTimeout의 콜백 함수의 평가와 실행은 JS엔진이 담당하지만
호출 스케줄링을 위한 타이머 설정과 콜백 함수의 등록은 부라우저(NodeJS)가 담당한다.
이를 위해 부라우저 환경은 태스크 큐와 이벤트 루프를 제공한다.
비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보괸되는 영역.
콜 스택에 현재 실행 중인 태스크가 있는지 그리고 태스크 큐에 대기중인 함수가 있는지 계속 확인한다.
만약 콜 스택이 비어 있고 태스크 큐에 대기 중인 함수가 있다면 이벤트 루프는 순차적으로
태스크 큐에 대기 중인 함수를 콜 스택으로 이동시킨다. 이때 콜스택에서 실행된다.
즉, 태크스 큐에 일시 보관된 함수들은 비동기 처리 방식으로 동작한다.
비동기 함수인 콜백함수는 태스크 큐에 푸시되어 대기하다가 콜 스택이 비게되면,
즉, 전역 코드 및 명시적으로 호출된 함수가 모두 종료되면(이것은 이벤트 루프가 확인한다.)
콜 스택에 푸시되어 실행된다.
By chatGPT :
이것은 특정 스레드에서 이벤트 루프를 만들어서 각 이벤트를 처리하는 것을 의미합니다.
이벤트 루프는 어플리케이션에서 일어나는 사용자 이벤트, 네트워크 이벤트 등을 기다리고 처리하는 루프입니다.
예를 들어, GUI 기반의 어플리케이션에서는 사용자가 어플리케이션에서 일어난 이벤트 (예: 버튼 클릭)을
처리하는 데 사용될 수 있습니다.
이벤트 루프는 계속해서 이벤트를 기다리고 있으며, 새로운 이벤트가 발생할 때마다 적절한 처리를 수행합니다.
이벤트 루프는 다양한 프로그래밍 언어와 프레임워크에서 지원되며,
비동기 프로그래밍과 다중 스레드 프로그래밍에서 많이 사용됩니다.
동기는 순서대로 작업이 진행되고 비동기는 동시다발적으로 진행된다고 할 수 있는데,
비동기적으로 진행되면 대기없이 빠른 작업 처리가 가능하기에 당연히 좋다고 볼 수 있지만,
동시에 자원을 사용한다면, 자원의 일관성 즉 원치 않은 자원의 망가짐이 발생 할 수 있다.
때문에 개발자는 비동기적으로 프로그래밍을 최대한 구현하되
이로 인한 문제들이 발생 하지 않도록 "동기화"를 시켜줘야 한다.
Drink, Cashier, ListQueue, Barista, Manager를 클래스로 구성하여
각자의 상태와 역할을 분배하고 협력할 수 있도록 한다.
Drink 클래스
// 주분 받은 음료의 상태를 가진다.
class Drink {
constructor(number) {
this.number = number;
this.runTime = 0;
this.name = this.getName();
this.endTime = this.getEndTime();
this.state = false;
}
getName() {
if (this.number === 1) return "아메리카노";
else if (this.number === 2) return "카페라떼";
else if (this.number === 3) return "프라푸치노";
}
getEndTime() {
if (this.number === 1) return 3;
else if (this.number === 2) return 5;
else if (this.number === 3) return 10;
}
changeState() {
if (this.runTime === this.endTime) return true;
else if (this.runTime !== this.endTime) return false;
}
}
// 주문을 받으면 큐 리스트에 전달한다.
class Cashier {
constructor(inputStr) {
this.inputStr = inputStr;
[this.number, this.quantity] = this.inputStr.split(":");
this.orderList = this.getOrder();
}
getOrder() {
const orderList = [];
for (let i = 1; i <= this.quantity; i++) {
orderList.push(this.number);
}
return orderList;
}
}
// 주문 받은 정보를 큐 스택에 순차적으로 채워준다.
class listQueue {
constructor() {
this.queue = [];
}
initOrder(str) {
const newOrder = new Cashier(str).orderList;
newOrder.map((v) => this.queue.push(v));
return this.queue;
}
showOrderList() {
return this.queue;
}
}
// 제작중인 스택을 가지고 커피를 만든다.
// 제작 시작과 종료시 이벤트(?) 호출한다.
class Barista {
constructor() {
this.callStack = [];
this.finishStack = [];
}
pushDrink(number) {
if (this.callStack.length < 2) {
this.callStack.push(new Drink(number));
return this.callStack;
} else if (this.callStack.length >= 2) {
return;
}
}
makeDrink() {
for (let i = 0; i < this.callStack.length; i++) {
if (this.callStack[i].runTime === 0) {
console.log(`${this.callStack[i].name} 시작`);
}
this.callStack[i].runTime++;
if (this.callStack[i].runTime === this.callStack[i].endTime) {
console.log(`${this.callStack[i].name} 완성`);
const finishDrink = this.callStack.splice(i, 1);
this.finishStack.push(finishDrink[0].name);
}
}
}
}
// 전체적인 관리자다
// 이벤트 루프를 돌며 리스트큐와 바리스타 스택을 1초마다 체크한다.
// 주문 리스트가 존재하고 바리스타 스택이 비어있으면 제작 지시를 한다.
class Manager {
constructor(listQueue) {
this.listQueue = listQueue;
this.barista = new Barista();
this.callStack = this.barista.callStack;
this.finishStack = this.barista.finishStack;
}
eventLoop() {
let i = 1;
const timer = setInterval(() => {
this.runtime(i);
console.log(this.listQueue);
this.checkQueueList();
this.commandMake();
i++;
if (this.listQueue.length === 0 && this.callStack.length === 0) {
console.log(`\n모든 메뉴가 완성되었습니다.\n`);
clearInterval(timer);
}
}, 1000);
}
checkQueueList() {
if (this.listQueue.length > 0 && this.callStack.length < 2) {
const makeDrink = this.listQueue.shift();
this.barista.pushDrink(Number(makeDrink));
} else if (this.listQueue.length === 0) {
return;
}
}
commandMake() {
this.barista.makeDrink();
}
runtime(i) {
console.log(`\x1b[30m\n-----------------${i}초-------------------\x1b[0m`);
}
init() {
this.eventLoop();
}
}
const queue = new listQueue();
queue.initOrder("1:2");
queue.initOrder("3:2");
queue.initOrder("2:2");
const start = new Manager(queue.showOrderList());
start.init();