동기처리와 비동기처리 #42

세나정·2023년 1월 25일
1

함수 실행 컨텍스트

함수를 호출 했을 때엔 함수 코드가 평가되어 함수 실행 컨텍스트가 생성됨
이렇게 생성된 함수 실행 컨텍스트는 콜 스택이라고 불리는 실행 컨텍스트 Stack에 푸시가 되고 코드가 실행 된다.
당연하게도 함수 코드의 실행이 종료되면 함수 실행 컨텍스트는 실행 컨텍스트 Stack에서 Pop으로 제거가 됨

실행 컨텍스트 Stack 예시

다음 두 가지 함수가 실행되는 예시를 보자!

foo, bar 순으로 선언을 하고 호출을 하는데
실행 컨텍스트 Stack에 1. foo 함수 실행 컨텍스트, 2. bar 실행 컨텍스트가 들어가고 나가고 하는 모습을 볼 수 있다.
1. (전역 코드 평가 시작) 전역 실행 컨텍스트 push
2. 전역 실행 컨텍스트에 foo 함수 실행 컨텍스트 push (실행)
3. 실행이 종료 됐으므로 foo 함수 실행 컨텍스트 pop
4. 전역 실행 컨텍스트에 bar 함수 실행 컨텍스트 push (실행)
5. 실행이 종료 됐으므로 bar 함수 실행 컨텍스트 pop

여기서 각 함수의 함수 실행 컨텍스트는 "함수 코드 평가 과정"에서 생성된다.
결론적으로, 함수의 실행순서는 실행 컨텍스트 Stack에 관리한다.

중요한 점

자바스크립트 엔진은 단 하나의 실행 컨텍스트 Stack을 갖는다

이 말은 함수를 실행할 수 있는 창구가 한 개 뿐이며 곧, "2개 이상의 함수를 동시에 실행할 수 없다는 것을 의미"
실행 컨텍스트 stack에서의 최상위 요소들 (실행 중인 애)을 제외 한다면 모두실행 대기중인 task들이란 것이다. 그렇기에 앞에 함수가 종료 (pop) 됐을 때에 다음 애가 실행이 된다.

이렇게 자바스크립트는 한 번에 하나의 태스크만 수행하는 "싱글 스레드" 방식으로 동작

싱글 스레드 방식은 한 번에 하나의 태스크만을 수행할 수 있기 때문에 처리에 시간이 걸린다면 블로킹 (blocking, 작업중단)이 발생한다.

- 동기처리

싱글 스레드의 블록킹 예시

다음 코드를 보면, 맨 처음 sleep 함수의 호출이 이루어지고 인자로는 foo함수가 전달이 된다.

그 후 delayUntil이라는 변수에 현재시간 + 3초라는 값을 대입한 후에
3초가 지나기 전까진 Date.now()함수가 적용이 되다 그 후에 delayUntil값이 더 작게 되면 그때서야 인자로 전달한 콜백함수 foo가 실행된다.


이런 상황처럼 태스크가 종료할 때까지 다음 태스크가 대기하는 방식을 동기처리라고한다. 즉, 앞선 태스크가 종료할 때까지 이후 태스크들이 블로킹 된다.

Q1. 여기서 동기 처리 방식의 장점은?

실행순서가 보장된다

- 비동기 처리

그러면 이제 위 예제를 타이머 함수인 setTimeout을 사용하여 수정해보자

setTimeout 함수도 sleep함수처럼 일정 시간이 경과한 이후 콜백함수가 실행 되지만, setTimeout이후 태스크를 블로킹 하는 것이 아니라 곧바로 실행하는 걸 볼 수 있다.

이렇게 실행중인 태스크가 종료되지 않은 상태에도 다음 태스크를 실행하는 방식을 비동기 처리방식이라고 한다.

Q2. 여기서 비동기처리를 수행하는 함수들을 뭐라고 하고 그 함수들의 단점 세 가지는?

비동기함수, 콜백 패턴을 사용하므로 이럴 경우 콜백 헬을 발생시켜 가독성을 나쁘게하고, 비동기 처리 중 발생한 에러 처리의 곤란, 비동기 처리의 한계

Q3. 타이머 함수처럼 비동기 처리 방식으로 동작하는 함수 3가지는?

setTimeout, setInterval, Http 요청, 이벤트 핸들러 (단, 커스텀 이벤트 디스패치나 click, blur, focus 등을 호출하면 태스크큐를 거치지 않고 즉시 호출 (즉, 동기 처리 방식)

마지막으로 비동기처리는 이벤트 루프와 태스크 큐와 깊은 관계가 있다.


- 이벤트 루프와 태스크 큐

- 이벤트 루프와 브라우저 환경

자바스크립트 엔진의 2가지 영역

- (콜) 스택

앞에서 설명한 것처럼 "함수 코드 평가 과정"에서 생성된 실행 컨텍스트 Stack을 의미한다.

(함수를 호출시 함수 실행 컨텍스트가 순차적으로 실행 컨텍스트 Stack에 push 되고 실행이 종료되어 pop 되기 전까지는 다른 어떤 태스크도 실행되지 않음.)

- 힙

힙은 "객체가 저장되는 메모리 공간"을 의미한다. 스택의 요소인 함수 실행 컨텍스트는 힙에 저장된 객체를 참조한다.

(메모리에 값을 저장하려면 메모리 공간의 크기를 결정한 후에 저장해야하지만 객체는 원시 값과는 달리 크기가 정해져 있지 않으므로 할당해야 할 메모리 크기가 런타임에 따라 결정됨 (동적할당) 따라서 객체가 저장되는 메모리 공간인 힙은 구조화 되어 있지 않다는 특징이 있음.)

이렇듯 자바스크립트 엔진은 단순히 태스크가 들어오면 콜 스택에 따라 순차적으로 실행할 뿐이고 비동기 처리에서 모든 처리 (소스코드의 평가와 실행 제외)는 자바스크립트 엔진을 구동하는 브라우저 또는 Node.js (작은 추가 엔진?)가 담당한다.

예를 들어,
비동기 방식으로 동작하는 setTimeout의 콜백 함수의 평가와 실행은 자바스크립트 엔진이 담당하지만,
호출 스케줄링을 위한 타이머 설정과 콜백 함수의 등록은 브라우저 또는 Node.js가 담당

이를위해 브라우저 환경은 태스크 큐와 이벤트 루프를 제공

브라우저 환경의 태스크 큐와 이벤트 루프

- 태스크 큐

setTimeout이나 setInterval 같은 비동기 함수의 "콜백함수" 또는 이벤트 핸들러가 일시적으로 보관되는 영역

태스크 큐와는 별도로 프로미스의 후속 처리 메서드의 콜백 함수가 일시적으로 보관되는 마이크로태스크 큐도 존재함

- 이벤트 루프

스택에 현재 실행중인 컨텍스트가 있는지 그리고 태스크 큐에 대기중인 함수 (콜백함수, 이벤트 핸들러)가 있는지 반복해서 확인

만약 스택이 비어있고 태스크 큐에 대기중인 함수가 있다면 이벤트 루프는 FIFO형식으로 태스크 큐 -> 스택으로 이동시킴

이때 스택으로 이동한 함수는 스택이 비어있으므로 바로 실행된다. 즉, 태스크 큐에 일시 보관된 함수들은 비동기 처리 방식으로 동작한다.

- 예제

Q4. 다음 중 foo 함수와 bar 함수 중에 먼저 실행되는 함수는?

가장 처음 콜 스택에 push 되는 것은 setTimeout이 맞지만
setTimeout과 setInterval 같은 함수의 "콜백 함수"는 태스크 큐로 이동하기 때문에
foo 함수는 태스크 큐로 이동을 하게 되고 (여기서 0이라고 했어도 실제로는 4ms 즉 0.004초의 시간이 걸림)
이제, setTimeout의 실행이 종료됐으므로 pop되고
곧바로 bar함수가 push되고 종료시 pop된다.
그 다음에서야 태스크큐에 존재하던 foo함수가 스택에 push되고 pop된다.
(즉, setTimeout -> bar -> foo)

상세 해설

  1. 전역 코드가 평가 되어 전역 실행 컨텍스트 가 생성되고 콜 스택에 푸시
  2. setTimeout의 함수 실행 컨텍스트가 생성되고 콜스택에 푸시, 가장 먼저 실행됨 (브라우저의 Web API(호스트객체)인 타이머 함수도 함수이므로 함수 실행 컨텍스트를 생성)
  3. setTimeout 함수가 콜백함수를 호출 스케줄링 (카운트 다운) 하고 곧 바로 종료되기 때문에 Pop됨, "여기서 호출 스케줄링이 끝나면 콜백함수를 태스크 큐에 푸시하는 것은 브라우저의 역할"
  4. 이제 브라우저는 동시에 두 가지 역할을 함
    4-1. 타이머의 만료를 기다림 (하지만 setTimeout 함수로 호출스케줄링한 콜백 함수는 정확히 지연 시간후에 호출 된다는 보장 X, 카운트다운이 끝난 후 태스크 큐에서 대기를 하지만 "콜스택이 비어야만 호출되기 때문")
    4-2. bar 함수가 호출되어 bar함수의 함수 실행 컨텍스트가 생성되고 콜 스택에 Push 되고 실행되고 종료되면 Pop됨 (타이머가 끝났다면 foo 함수는 아직 태스크 큐에서 대기)
  5. 이제 전역 코드 실행이 끝났으므로 전역 실행 컨텍스트 도 콜 스택에서 Pop됨 그렇기에 정말 텅텅 빔
  6. 이벤트 루프에 의해 콜 스택이 텅 비어있음을 감지하면 태스크 큐의 foo 함수가 이벤트 루프에 의해 콜스택에 Push됨 즉, 다시 전역 실행 컨텍스트 가 콜 스택에 Push 되고 그 후 foo함수의 Push됨. 그 후 마찬가지로 둘 다 Pop

결론적으로, 전역 코드 및 명시적 호출 함수가 모두 종료되어야 태스크 큐에 대기하던 함수들이 실행

자바스크립트 엔진 : 싱글 스레드 방식
브라우저 : 멀티 스테드 방식

이렇게 브라우저와 자바스크립트 엔진이 협력하여 비동기 함수인 setTimeout 함수를 실행함

profile
기록, 꺼내 쓸 수 있는 즐거움

0개의 댓글

관련 채용 정보