LHJ

I'm a FE developer.

5. BLOCK, NONBLOCK & SYNC, ASYNC

21 Jun 2020 » codespitz

5. BLOCK, NONBLOCK & SYNC, ASYNC

FLOW를 막고 있는 것이 바로 BLOCK.
BLOCKING이 일어나고 있는 상태에선 컴퓨터가 죽은 것처럼 보인다.(CPU)
그래서 이 때 내 컴퓨터가 이대로 꺼질 것인가 아니면 다시 살아날 것인가, 불안불안하다.
이렇기 때문에 BLOCKING을 제거하는 것이 개발자들의 제일 큰 화두이다.

모든 프로그래밍 소스는 BLOCKING을 일으킨다.
그럼 NON-BLOCKING은 뭐야?
상대적인 것이다.
짧은 시간동안만 블로킹을 일으키면, 논블로킹이고 긴 시간동안 블로킹을 일으키면 블로킹인 것이다.
그럼 그 기준은 누가 정하는데? 사람마다 다 달라?
라고 생각하실지도 모르겠지만, 사실은 그렇지 않다.
VR은 16.6ms 를 권고사항으로 하고 있고, 브라우저는 5초 정도라고 하고 있다.
전화기는 1ms만 느려도 상대방 목소리가 다르게 들린다거나 하기 때문에 블로킹시간이 다른 거에 비해 훨씬 적다.

FLOW IS BLOCKING

프로그램이 실행되면 도중에 멈춰지지 않고 끝까지 실행된다.(CPU가 블로킹된다)

for (const i of (function*() {
    let i = 0;
    // yield가 실행될 때마다 iterator result object 반환
    while (true) yield i++;
})()) console.log(i);
// 제너레이터를 호출하면 유사 이터러블로 작동한다.
// 위 무한 루프는 플로우 블로킹을 일으킨다.

// scripot timeout
// 플랫폼의 안정성을 위해 블록되는 시간이 길면 강제 종료시킨다.

BLOCKING FUNCTION

점유하는 시간만큼 블록을 일으키는 함수

const f = v => {
    let i = 0;
    while(i++ < v);
    return i;
};
// 아래는 블로킹을 유발 안할 것이다.
f(10);
// 아래는 블로킹을 유발할 것이다.
f(10000000000000000);
// 이것이 블로킹 함수다.

함수를 짰을 때 이 함수가 블로킹 함수인지 아닌지는, 함수에 전달하는 인자에 달려있다.

배열을 루프돌리는 함수라면, 배열의 크기에 따라 블로킹 함수인지 아닌지가 달려있다. 만약에 배열 대신에 받아오는 인자가 해시맵(셋)이었다면, 값을 해시화시키고 해시셋 있는지만 조사하면 끝이기 때문에 일정 비용으로 아무리 많은 큰 집합의 해시셋이 와도 똑같은 비용으로 처리할 수 있다.

자바의 해시셋이라는 개념이 자바스크립트에도 위크셋이라고 있다.
위크셋을 쓰면 아무리 위크셋에 들어가 있는 객체 수가 많아져도 위크셋에 값이 있는지를 조사할 때는 똑같은 비용으로 조사할 수 있다.
이는 블로킹 함수가 아니다.
많거나 적거나 똑같은 블로킹 시간이기 때문에 블로킹함수라고 불리지 않는다.

아무튼 우리는 자기도 모르게 블로킹 함수로 짠다.
위의 소스도 블로킹 함수란걸 보면 우리가 그렇다는 걸 알 수 있다.
일단 여러분이 블로킹 함수로 어플리케이션을 만들면, 해당 어플리케이션 상황에 따라 굉장히 긴 블로킹이 유발될 수도 있는 것이다.
그래서 블로킹 함수 관리가 정말 중요하다.
밑에 깔린 유틸리티 함수 몇 개가 블로킹으로 되어 있다는 이유로 시스템 전체가 블로킹에 빠질 수도 있다.

위 소스에 max 같은 것을 조건으로 달지 않는 이상, (max > 100 이라면 throw 라던지..) 어디선가 블로킹을 일으킬지 모른다는 것이다.


아니 저렇게 안 짜는 사람도 있나요?
네, 저렇게 안 짭니다. 의사들은.
블로킹 함수가 되지 않도록 처음부터 유의하면서 짠다.

함수 하나를 짜더라도 프로 정신을 가지고 짜자.
난 엔터프라이즈 급으로 일하는 사람이다. 라는 생각으로 짜자.


  1. 배열순회, 정렬 - 배열 크기에 따라
  2. DOM 순회 - DOM의 하위 구조에 따라
  3. 이미지 프로세싱 - 이미지 크기에 따라

아니 그러면, 배열 크기가 크면? DOM 하위 구조가 크면? 이미지 크기가 크면? 어떻게 블로킹에 빠지지 않게 코드를 짜라는 거야?
특정 단위로 쪼개서 setInterval로 작업을 한 다음에 나중에 합치면 될 거 아냐?
이런건 경험이 아니다.
단지, 제대로 안 배웠다는 것이다.
제대로 배우면 위와 같은 것들을 고려 안할리 없다.

BLOCKING EVASION

블로킹을 피하는 기술

블로킹의 문제점

  1. 독점적인 cpu 점유로 인해 모든 동작이 정지됨 (의문 : 난 6코어 인데? 멀티 코어인데? 이럴리 있어?)
  2. 타임아웃 체크에 의해 프로그래밍이 강제 중단됨
    요즘은 OS에 제어되는 코드로 짜게끔 강제된다.
    만약 그렇지 않은 코드로 짜게되면 블루스크린 뜨는 것.
    맥OS는 많이 제한되어있어서 어플 올리기 힘든 것. 맥 OS에 제어되는 코드로만 짜야되니깐.

블로킹 함수로 짠다면 얼마나 많은 블로킹이 발생할지 예측할 수 없다.

// some 함수가 블로킹이고 other 함수가 블로킹이면 당연히 f도 블로킹 함수
// 그리고 각각 얼마나 블로킹을 일으키는지 모른다.
// 실제 코드는 이렇게 생겼기 때문에, 구성 요소 중 하나만 블로킹이더라도 전체가 오염될 수 있다는 것이다.
const f = v => other(some(v), v = 2);
f(10);

블로킹 함수의 에러는 잡을 수가 없다. 그냥 죽기 때문에.
디버깅, 콘솔로그로도 안 뜬다.
이러면 비용 엄청 발생시키는 거. 이거 잡겠다고.

순차적인 실행

1(완료) -> 2(완료) -> 3(완료)

현대 시분할 운영체제의 동시 실행

1(조금) -> 2(조금) -> 3(조금) -> 1(조금) -> 2(조금) -> 3(조금) -> …

당연히 시분할 운영체제의 시간이 똑같은 일을 해도 순차적 실행보다 3배 이상 오래걸릴 때도 있다.
재수 없으면 4~5배 이상 차이나기도 한다.
순차적 실행이 훨씬 빠른데도 불구하고 시분할 운영체제로 하는 이유는 뭘까?
다 사용자들 때문이다.
사용자들이 지겨운걸 못 견뎌하기 때문.

음악이 플레이되는 척 하면서, 프린팅이 되는 척 하면서, 영화가 재생되어야 해.

이런 느낌이다.
조금씩 처리되고 전체 완료가 더 오래걸린다고 하더라도 그걸 원한다.
그래서 단일 CPU에 대한 시분할 운영체제가 탄생한 것이다. CPU가 여러개여도 각각 CPU에게 시분할 운영을 시킨다.

자바스크립트 쓰레드

그렇다면 자바스크립트 스레드는 어떻게 생겼을까?
흔히 자바스크립트가 싱글 스레드라고 하는데, 이것은 굉장히 잘못된 생각이다.
자바스크립트 브라우저에는 수많은 스레드가 돌고 있다.
일반적으로 스레드 추적기 같은 거 돌려보면, 네이버 같은 페이지 보면 스레드 15개 정도 떠있다.

자바스크립트 스레드가 싱글 스레드가 아니라, 자바스크립트에서 UI에 관련된 처리를 하고, 자바스크립트란 스크립트 처리하는 부분이 바로 싱글 스레드로 되어 있다.
그런데 이것은 자바스크립트가 특이한 것이 아니다.
iOS, 안드로이드 다 이렇게 되어 있고, 윈도우 관리 코드로 되어있는 어플리케이션을 유니버셜 앱이라고 하는데, 얘도 이렇게 되어있다.

현대의 거의 모든 OS는 UI를 건드리거나 메인 스크립트 작동을 싱글 스레드로 하고 있다.
그리고 우리는 MAIN UI THREAD를 감시한다.
얘가 얼마나 블로킹을 발생시키는 지를 감시한다.

반면에 여러분이 AJAX 로딩을 20개로 걸어놨어. 얘네는 다 다른 스레드로 뜬다.
MAIN UI THREAD가 아니라 다른 스레드로 뜬다.(BACKGROUND THREAD)
최근에 HTML5가 등장한 이후엔 이러한 브라우저 내장 스레드 말고도 우리가 직접 스레드를 띄울 수 있는 WEB WORKER라는 기능이 있다. WEB WORKER를 이용하면 우리가 자바스크립트에서도 별도의 스레드를 또 띄울 수 있다.

그에 비해서 자바스크립트의 MAIN UI THREAD 가 너무 희안하니까, 다른 것들이 비해서..
얘를 좀 더 깊이 살펴보자.
이 스레드는 어떻게 작동하냐면, 이 스레드는 무한 루프를 돌고 있다.
그리고 이 스레드가 바라보고 있는 배열이 있다.
이 배열엔 자바스크립트의 명령을 저장을 하는데, 저장될 때 이 명령은 몇 초에 실행되어라 라는 타임스탬프가 붙게된다.
예를 들어, 이 명령은 시작하자마자 실행해, 저거는 2초 이따가 실행해,.. 이런 타임스탬프가 붙어있다.

인간이 이해하기 쉬우라고 시간이라는 말을 썼지만, 컴퓨터는 이렇게 인식한다.
이 명령어는 0 프레임에서 실행해, 이건 1 프레임에서 실행해,..
메인 UI 스레드에서는 자신만의 시간 - 루프 한바퀴 돌 때마다 1틱이라고 한다면, 5틱 때 몇 프레임을 실행해.. 이런 느낌인건가?
메인 UI 스레드는 계속 자신만의 시간을 돌리면서 해당 시간일 때 명령어를 실행해준다.

그래서 우리가 다수의 명령어를 이러한 시간축에다 배열해두면, 메인 UI 스레드는 자신만의 시간을 돌리면서 해당 명령어들을 확인한다.
클릭 이벤트 리스너가 프레임에 들어와서 대기 -> 메인 UI 스레드 시간축 돌리다가 발견 -> 실행
AJAX 완료 리스너가 프레임에 들어와서 대기 -> 메인 UI 스레드 시간축 돌리다가 발견 -> 실행

다른 스레드에서 실행의 결과를 메인 UI 스레드 프레임이 넣어놓고 메인 UI 스레드가 돌면서 받아들이는 느낌(?)인 것 같다. 이걸 멀티 스레드 패턴에선 서스팬션 페턴이라고 부른다.

메인 스레드는 QUEUE 에서 시간이되면 쌓인 명령들을 끄집어내서 읽기만 하고, 다른 쓰레드는 QUEUE에 다 적재만 한다.
DOM UI 처리기, 이벤트 처리기 모두 별도의 스레드인데, 얘네들은 클릭하는 순간 바로 다음 프레임에다가 명령을 적재해버린다.
그럼 메인 스레드가 돌면서 해당 명령들을 처리하는 것이다.

메인 스레드가 명령어 하나를 꺼내서 실행한 총 시간이 5초 이내여야 블로킹이 안 걸린다.
자바스크립트 기본 블록 이베이젼 방법은 타임을 slice해서 나눠서 프레임을 걸어주는 것이다.
각각의 프레임에서 5초만 안 넘어가면 되기 때문에, 타임을 슬라이스해서 다른 프레임들로 스프레드 해버리는 것이다.

TIME SLICING MANUAL

// 이 블로킹 함수를 수정해보자.
const looper = (n, f) => {
    for (let i = 0; i < n; i++) f(i);
};
looper(10, console.log)
looper(10000, console.log)
// 타임슬라이스 기법
const looper = (n, f, slice = 3) => {
    let limit = 0, i = 0;
    const runner = _ => {
        while (i < n) {
            if (limit++ < slice) f(i++);
            else {
                limit = 0;
                requestAnimationFrame(runner);
                break;
            }
        }
    };
    requestAnimationFrame(runner);
}

위 함수는 논블로킹으로 수정했다.
그런데, 새로 받아오는 f 함수가 블로킹 함수라면 어떻게할 것인가.
그리고, slice는 사람이 정해주는 것이다.
기계인데 이걸 사람이 정해서 줘도 되나?
기계가 알아서 하게끔 만드는 방법은 없을까?

const looper = (n, f, ms = 5000, i = 0) => {
    let old = performance.now();
    const runner = old => {
        while(i < n) {
            let curr = performance.now();
            if (curr - old < ms) f(i++);
            else {
                old = performance.now();
                requestAnimationFrame(runner(old));
                break;
            }
        }
    }
    runner(old);
}

SYNC, ASYNC

지금까지 공부한 개념들은 BLOCKING, NON-BLOCKING이다.
이것이 얼마나 해로운지도 알았다.
그래서 BLOCKING EVASION을 통해 NON-BLOCKING화 해준다.
우리는 BLOCKING을 피해야된다.

블로킹, 논블로킹은 그래도 FLOW의 일부이다.
조금은 쉽게 배울 수 있는 개념이다.
FLOW에 블로킹일 일어나는 것을 약간의 꼼수를 써서 해결할 뿐이지, 기본적으로 FLOW라 이해하기 쉽다.

그에비해 SYNC, ASYNC는 우리에게 굉장한 혼란을 준다.
혼란을 주는 이유는 FLOW를 어긋나게 만들기 때문이다.

SYNC : 서브루틴이 즉시 값을 반환함
ASYNC : 서브루틴이 콜백을 통해 값을 반환함

블록, 논블록과 상관 없다.
SYNC, ASYNC는 고작 이정도 개념이다.
그렇다면 ASYNC로 성립하는 것은 뭐다? 인자로 함수를 넘겼냐 안넘겼냐, 콜백함수를 넘겼냐 안넘겼냐, 해당 콜백함수가 어떻게 실행되는지는 상관 없다.
콜백함수를 넘겼다면 그건 바로 ASYNC이다.

  • SYNC 함수 예
      const double = v => v*2;
      console.log(double(2)); // 4
    
  • ASYNC 함수 예
      const double = (v, f) => f(v*2);
      double(2, console.log); // 4
    

    ASYNC 함수는 SYNC 함수와 다르게, 직접 리턴하지 않고 f 함수로 인자를 전달해준다.
    값을 직접 받을 수 없고 f를 통해서 받아야된다.

SYNC : 서브루틴이 즉시 값을 반환함

  • BLOCK : 즉시 플로우 제어권을 반환하지 않는다.
    SYNC 함수가 BLOCK인 경우에는 즉시 플로우 제어권을 반환하지 않는다.
    이것이 여지껏 우리가 다루고 있었던 문제.
    SYNC 함수가 값을 return하려고 하는데, 그 과정이 너무 오래걸려서 FLOW를 BLOCKING하고 있는 문제.
        // 아래 식은 싱크인데 블로킹인 함수다.
        // 함수에서 즉시 값을 return 하고 함수가 발생하는 동안 FLOW를 BLOCKING 한다.
        const sum = n => {
            let sum = 0;
            for (let i = 1; i <= n; i++) sum += i;
            return sum;
        }
        sum(100);
    
  • NON BLOCK : 즉시 플로우 제어권을 반환한다.
        // 싱크함수이면서 논블로킹 함수
        const sum = n => {
            const result = {isComplete: false};
            // SYNC지만 이번 프레임에서 BLOCK을 안 걸었다.
            // 다음 프레임에 BLOCK을 걸었다.
            // 다음 프레임에서 BLOCK을 얼마나 차지할지는 몰라도 이번 프레임에선 걸지 않았다.
            requestAnimationFrame(_=>{
                let sum = 0;
                for (let i=1; i<=n; i++) sum += i;
                result.isComplete = true;
                result.value = sum;
            });
            // result 객체가 즉시 반환된다.
            // 위에 할 일이 남았어도 아래 result를 즉시 반환하고 오늘은 퇴근해버리는 거랑 똑같다.  
            // 내일 할 일은 내일의 나에게로~!
            return result;
        }
      
    // 위의 식은 한 {} 안에 작성해서 같이 실행되는 것으로 보일지라도 아니다.
    // 여러 FRAME에 나뉘어서 실행되고 있는 것이다.
    

옛날 방식 API들은 SYNC이자 NON-BLOCK인 것들이 굉장히 많다.
그래서 아래와 같이 해야된다는 번거로움이 있다.

const sum = n => {
  const result = {isComplete: false};
  requestAnimationFrame(_=>{
      let sum = 0;
      for (let i=1; i<=n; i++) sum += i;
      result.isComplete = true;
      result.value = sum;
  });
  return result;
}

const result = sum(100);

// setInterval을 걸어서 위의 request~부분이 완료되었는지 아닌지를 계속 물어보는 것이다.
const id = setInterval(() => {
    if (result.isComplete) {
        clearInterval(id);
        console.log(result.value);
    }
}, 10);

이것이 바로 SYNC, NON BLOCKING API이다.
이런 소스로 작업이 완료되었는지 아닌지를 체크해줘야 한다. 이런 기능을 가진 자바스크립트 API가 있나?
DOM에 있다.
img element는 isComplete라는 속성을 가지고 있다.

<head>
    <script type="text/javascript">
        function OnLoadImage (img) {
            var isComplete = img.complete;
            alert(isComplete);
        }
    </script>
</head>
<body>
    <img src="picture.gif" onload="OnLoadImage (this);" />
</body>
<!DOCTYPE html>
<html>
<body>

<img id="myImg" src="compman.gif" alt="Computerman" width="107" height="98">

<p>Click the button to display whether the browser is finished loading the image or not.</p>

<p>This property returns true if the image has finished loaded, and false if not.</p>

<button onclick="myFunction()">Try it</button>

<p id="demo"></p>

<script>
function myFunction() {
  var x = document.getElementById("myImg").complete;
  document.getElementById("demo").innerHTML = x;
}
</script>

</body>
</html>

현재는 모던 API로 바뀌어서 img 태그에 onload를 걸면, 완료를 콜백 형태로 받을 수 있다.
그런데 이거 안 쓰고 위에처럼 img 선택자에 .complete를 하면, img가 로딩되었을 때 true로 바뀐다.
왜 이런 API들을 만들었을까?
시대의 흐름이었다.
그 당시에는 이런 식의 API를 만드는 것이 대세였다. - SYNC + NON BLOCKING

싱크 논블로킹은 다 좋은데, 싱크 논블로킹으로 리턴된 값을 조사하는 것도 일반적인 싱크로직으론 안된다는 것이다.

옛날에는 이렇게 하는 것이 좋은 것이라고 생각했다.
그 당시엔 사람들이 완결성에 집착을 하고 있었기 때문에..
함수를 호출했으면 수학적으로 봤을 때, return이 나와야해.
값이 return 되었지만 중간에 함수가 논블로킹으로 백그라운드에서 오래동안 일할 수도 있지.
라고 발상을 해보면 싱크, 논블록을 만들게 된다.
그럼 결과값이 언제 나올지 모르니깐 계속 감시해야돼.
이 당시 스타일이 그랬던 것. 이게 잘못된 게 아님.
심지어 지금도 이런 API는 존재하고 있다.

  • SYNC 함수라고 다 블로킹이 아니다.

SYNC + BLOCK은 일반적으로 우리가 많이 보는 거지만, SYNC + NON-BLOCK도 흔한 API이다.

ASYNC : 서브루틴이 콜백을 통해 값을 반환한다.

여기서부터 사람들의 마음이 좀 유연해졌다.
이게 왜 유연성을 만드는 건지는 다음 FUNCTION 스터디를 해야 좀 이해가 갈 거다.
ASYNC를 함수로 보내면 왜 유연해지는지 이해하려면 함수에 대한 이해도가 높아야 한다.
현재 여러분들은 콜백의 의미가 무엇인지 잘 알지 못한다.
그런데 함수를 배우면 ‘아 이것 때문에 SYNC - NON BLOCK을 하는 것보다 ASYNC, NON BLOCK을 하는 게 훨씬 낫구나’라는 결론에 도달할 수 있다.

  • ASYNC BLOCK : 즉시 플로우 제어권을 반환하지 않는다.
        const sum = (n, f) => {
            let sum = 0;
            for (let i=0; i<=n; i++) sum += i;
            return f(sum);
        };
        sum(10, console.log);
        console.log(123);
    

    ASYNC라고 BLOCK을 일으키지 않는 것은 아니다.
    ASYNC도 위와 같이 BLOCK을 일으키도록 짤 수 있다.
    return을 함수의 호출로 한다는 것만 ASYNC의 조건에 부합되는 거지 식 자체는 BLOCKING을 하고 있다.

  • ASYNC NON BLOCK : 즉시 플로우 제어권을 반환한다. 우리가 정말 만들고 싶은 소스이다.
        const sum = (n, f) => {
            requestAnimationFrame(_=>{
                let sum = 0;
                for (let i=1; i<=n; i++) sum += i;
                f(sum);
            });
        };
        sum(10, console.log);
        console.log(123);
    

  • SYNC, BLOCK : 평소 우리 코드의 모습..nomal API, lagacy API..
  • ASYNC, NON-BLOCK : AJAX, 이벤트 리스터 - 콜백을 넘겨서 결과를 받는데, NON-BLOCK 하게 일어나고 있다.
    우리가 가장 이상적으로 바라보는 API의 형태이고 우리도 이걸 지향해 소스를 짜야된다.
  • SYNC, NON-BLOCK : 배척할 대상은 아니다. 아직도 많이 쓰고 더 유리할 때도 있다.
  • ASYNC, BLOCK : TRAP!!!! BLOCK은 BLOCK대로 일으키고 값도 return 안함. 최악.

유사 함정들 : SIMILAR ASYNC - BLOCK

ASYNC BLOCK이 함정이라고 했지?
그럼 우리가 코드를 짤 때 가장 주의해야하는 것은 ASYNC BLOCK이다.
SYNC BLOCK은 어느정도 감당이 된단 말이지.
Math.sin, Math.random 다 느리잖아?
Math.random 속도가 굉장히 느리기 때문에 배열 1000개정도 미리 잡아놓고 돌려도 성능이 10배씩 빨라질 때도 있다.
슈팅게임 같은거 만들 때 random함수 쓰지 말고 배열 루프 돌려서 랜덤으로 나오게 하는… 다른걸 쓰면 훨씬 빨라진다.
random은 500개부터 잘 인식을 못한다. 반복되고 있어도.
Math.sin, Math.cos 이런 것도 배열로 만들어서 끄내는게 더 빠르긴 한데, 라디안 스펙트럼이 커서 배열의 범위가 커지기 때문에 이득이 그렇게 크지 않다는 것이다.
하지만 random은 배열로 하는 게 이득이 훨씬 크다.
어쨌든 Math.sin, Math.random 이런 것들도 블로킹 함수라는 것이다.
즉, 우리는 블로킹 함수와 공존하는 법을 알고 있다.
노멀 API를 만드는 사람들은 우리의 적이 아니다.
그냥 그 사람의 개발 실력이 그 정도일 뿐.

하지만 ASYNC BLOCK은 우리의 적이다.

// 아래의 식은 오늘의 나는 살지만, 내일의 내가 죽는다.
// 현재 프레임은 괜찮을 수 있으나 다음 프레임에서 죽어버린다.
// 아래와 같은 코드를 짜지 않도록 항상 주의해야 한다.
const sum = (n, f) => {
    requestAnimationFrame(_ => {
        let sum = 0;
        for (let i=1; i<=n; i++) sum += i;
        f(sum);
    });
};
sum(100000000, console.log);
console.log(123);

ASYNC API가 나중엔 PROMISE 같은 표준화되어있는 직렬 연결로 바뀐다.
ASYNC는 코드들이 서로 널뛰기하면서 실행된다.
순서를 분간할 수 없다.
그것을 위해 Promise 같은 것들이 순서대로 일어나는 것처럼 보이게 해준다. Promise, then으로 연결하면 최소한 그 안에선 순서대로 실행된다. 하지만 그렇다고 then으로 너무 많이 연결해놓으면 나중에 폭탄이 터질 수도 있다. async / await 같은 애들로 더더욱 연결하고 그러면 시한폭탄이 될지도 모른다.

ASYNC / AWAIT 까지 도입한 회사들은 그래서 더더욱 조심해야된다.

스레드 - CPU의 코어 수의 2배까지만 잡는 것이 일반적.
그 이상 잡으면 멀티 시분할 전환하는 것 때문에 엄청 느려진다.
그래서 위 소스도 유사 블로킹이 일어난다.

이러한 것들을 방지하기 위해서 다른 언어에선 스레드 풀이라는 것들을 운영한다.
스레드 풀에서 가용한 스레드만 작동하고 나머진 대기해. 이런 개념?
위에서 worker를 쓰려면 worker를 위한 스레드 풀을 만들어놓고 사용해야 안전하다.

평소에 SYNC, BLOCK으로 소스를 짜고 있지만 ASYNC - NON-BLOCK 으로 소스를 짜도록 되도록이면 노력해야 되고,
ASYNC, NONBLOCK으로 짜면 필연적으로 어떤 일이 발생한다?
코드가 널뛴다. 위에서 아래로 읽을 수가 없다. 어떤 놈이 먼저 실행됐는지 모른다.
이것 때문에 우리 의식이 혼란스럽고 함수가 널뛰는 느낌을 배제하려고 Promise 같은 것을 사용하는 것이다.
그렇게하면 우리가 의식이 정리되기 때문.

그렇기 때문에 ASYNC NON BLOCKING의 medern api는 Promise로 한단계 더 나아가는 것이다.
2017년 이후 자바스크립트는 ASYNC NONBLOCKING API가 안 나오고 Async Non Blocking의 Promise버전만 나온다.
대표적으로 Fetch
요즘은 통신하기 위해서 Fetch라는 객체를 사용한다.
Fetch는 처음부터 Promise를 리턴하게 되어있다.