동기 & 비동기 / 블로킹 & 논블로킹
포스트
취소

동기 & 비동기 / 블로킹 & 논블로킹

해당 포스트는 JavaScript기준으로 동기 & 비동기 / 블로킹 & 논블로킹의 개념을 작성한 포스트입니다.
주관적인 생각을 정리해서 잘못 설명된 부분이 있을 수 있습니다.

🚏 동기 & 비동기

동기와 비동기를 가르는 핵심적인 차이는 실행 순서입니다.

동기는 위에서부터 아래로 흐름에 맞게 실행합니다. ( 일반적인 함수 호출 ( 결과를 기다림 ) )
비동기는 실행을 기다리지 않고 다음으로 넘어갑니다. ( settimeout같은 함수의 실행 ( 결과를 기다리지 않고 콜백 함수로 처리함 ) )

0️⃣ 동기 ( Synchronous )

JavaScript 기준 일반적인 코드는 동기적으로 실행이 됩니다.
아래와 같은 코드로 설명하자면 (1)에서 result의 값은 sum()에 의해서 결정됩니다.
그리고 (2)에서는 result를 사용하고 있죠.

만약 비동기적으로 수행해서 결과에 대한 관심이 없게 수행된다면(sum()의 결과를 기다리지 않고 바로 다음이 수행된다면) (2)에서 undefined가 나오게 될겁니다.
뭔가 저희가 일반적으로 생각하는 실행의 순서와 다른 결과가 나오게 되죠.

1
2
3
4
5
6
7
const sum = (...args) => args.reduce((acc, curr) => acc + curr);

// (1)
const result = sum(1, 2, 3, 4, 5);

// (2)
console.log(result); // 15

위처럼 함수나 다른 요청에 의한 결과에 대한 관심이 있고 그것을 받아서 결과를 이용해 다른 처리를 하는 것을 동기라고 합니다.
즉, 실행 순서를 지키면서 동작하죠.

1️⃣ 비동기 ( Asynchronous )

실행 순서를 지키지 않고 현재 실행을 책임지지 않고 다음 실행으로 넘어갑니다.
결과에 대한 처리는 콜백 함수를 통해서 처리하고 즉, 결과를 기다리지 않고 다른 동작을 수행합니다.

아래 코드에서 timer()를 먼저 실행했지만 Promise를 이용해서 timer()의 결과를 기다리지 않고 다른 작업((1))을 수행합니다.
그리고 타이머에 의한 결과는 then()을 통해 callback으로 처리합니다.
따라서 해당 코드는 timer()를 수행할 때 결과를 기다리지 않고 다른 처리를 해도 문제 없이 동작하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const sum = (...args) => args.reduce((acc, curr) => acc + curr);

const timer = (wait, ...args) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const random = Math.floor(Math.random() * 2);

      if (random % 2) {
        resolve(sum(...args));
      } else {
        reject("에러!!");
      }
    }, wait);
  });
};

timer(1000, 1, 2, 3, 4, 5)
  .then((res) => console.log(res))
  .catch(console.error);

// (1)
console.log("1. 타이머를 기다리지 않고 다른 작업 수행!!!");
console.log("2. 타이머를 기다리지 않고 다른 작업 수행!!!");
console.log("3. 타이머를 기다리지 않고 다른 작업 수행!!!");

/**
 * 1. 타이머를 기다리지 않고 다른 작업 수행!!!
 * 2. 타이머를 기다리지 않고 다른 작업 수행!!!
 * 3. 타이머를 기다리지 않고 다른 작업 수행!!!
 * 15 || "에러!!"
 */

이렇게 실행 순서를 지키지 않고 다음 동작을 수행하는 것을 비동기라고 합니다.
대표적으로 fetch()같은 ajax요청을 비동기적으로 수행합니다.
네트워크 요청은 여러 환경들에 따라서 속도가 달라지기 때문에 응답이 올때까지 아무것도 하지 않으면 그만큼의 시간이 낭비되기 때문이라고 알고 있습니다.
( ex) 게시글을 불러오는 동안 다른 게시글을 보지도 못하고 댓글도 못달고 좋아요도 못누른다면 사용자들이 답답해서 사용하지 않겠죠. )

🕹️ 블로킹 & 논블로킹

블로킹과 논블로킹를 가르는 핵심적인 차이는 제어권입니다.

블로킹은 작업을 수행하다가 함수같은 요청을 만나면 제어권을 넘겨줍니다. ( 즉, 제어권이 없는 본인은 아무것도 못하고 기다려야 합니다. )
논블로킹은 제어권을 넘겨주지 않고 알아서 수행하게 하고 본인은 다시 본인의 작업을 이어서 수행합니다.

0️⃣ 블로킹 ( Blocking )

작업을 수행하다가 다른 요청을 만나면 제어권을 넘겨줍니다.
그리고 제어권을 잃은 주체는 다른 코드를 실행하지 못하고 하염없이 결과를 기다립니다.

(1)에서 sum()을 만난 경우 제어권을 sum()에게 넘겨주고 결과를 기다립니다.
즉, 바로 (2)로 넘어가지 못한다는 의미죠.
그리고 sum()의 결과를 받으면 그때 (2)로 넘어갑니다.

1
2
3
4
5
6
7
const sum = (...args) => args.reduce((acc, curr) => acc + curr);

// (1)
const result = sum(1, 2, 3, 4, 5);

// (2)
console.log(result); // 15

이런 수행을 블로킹이라고 합니다. ( 제어권이 없어서 다음 수행을 못하도록 가로막히는거죠. )

1️⃣ 논블로킹 ( Non-Blocking )

작업을 수행하다가 다른 요청을 만나도 제어권은 본인이 갖고 있습니다.
그리고 요청은 다른 제어권에 의해서 알아서 수행하고 결과를 반환합니다.
( 다른 제어권이란 ajax 요청의 경우 서버를 의미하고, js의 경우 이벤트 루프와 태스크 큐를 의미하는 것 같습니다. ( 확실하지 않음 ) )

아래 코드도 timer()를 만나 이후에도 본인이 제어권을 갖고 있으니 실행만 시키고 실행 결과에 대한 신경을 쓰지 않고 본인 코드를 이어서 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const sum = (...args) => args.reduce((acc, curr) => acc + curr);

const timer = (wait, ...args) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const random = Math.floor(Math.random() * 2);

      if (random % 2) {
        resolve(sum(...args));
      } else {
        reject("에러!!");
      }
    }, wait);
  });
};

timer(1000, 1, 2, 3, 4, 5)
  .then((res) => console.log(res))
  .catch(console.error);

// (1)
console.log("1. 타이머를 기다리지 않고 다른 작업 수행!!!");
console.log("2. 타이머를 기다리지 않고 다른 작업 수행!!!");
console.log("3. 타이머를 기다리지 않고 다른 작업 수행!!!");

/**
 * 1. 타이머를 기다리지 않고 다른 작업 수행!!!
 * 2. 타이머를 기다리지 않고 다른 작업 수행!!!
 * 3. 타이머를 기다리지 않고 다른 작업 수행!!!
 * 15 || "에러!!"
 */

이런 수행을 논블로킹이라고 합니다. ( 결과에 신경쓰지 않고 제어권을 갖고 계속 수행하죠 )

🪄 동기 & 비동기 / 블로킹 & 논블로킹

0️⃣ 동기 + 블로킹

제어권을 넘겨주고 결과를 기다리고 결과가 오면 제어권을 받고 실행

일반적인 JavaScript의 동작 방식과 같습니다.

1️⃣ 동기 + 논블로킹

제어권은 본인이 갖고 계속 실행은 하지만 결과가 필요해서 결과를 계속 요청

대표적인 예시로 게임에서 맵을 다운받을 때라고 합니다.
제어권은 유저의 컴퓨터지만 프로그래스바를 보여줘야 하기 때문에 지속적으로 진행 상황을 요청

2️⃣ 비동기 + 블로킹

제어권을 넘겨주고 기다리는데 사실 결과가 필요가 없는 경우

사용하는 경우보다 개발자의 실수로 인해 사용되는 경우가 있다고 합니다.

3️⃣ 비동기 + 논블로킹

제어권은 본인이 갖고 계속 실행하되 결과에 대한 응답은 콜백 함수로 대체

결과가 궁금하지만 다른 실행을 계속 해야하는 경우 사용합니다.
( 서버로 부터 데이터를 요청을 하고 다른 실행은 계속하고 응답이 오면 콜백 함수로 실행 )

근데 JavaScriptasync/await비동기/논블로킹로 작동하지만 동기/블로킹처럼 보입니다.
아래 코드를 보면 뭔가 독특한 동작이 보입니다.
async/await을 통한 비동기 처리는 함수 내부에서 await 이후 코드를 즉시 실행하지 않습니다.
즉, 동기/블로킹처럼 코드의 실행 순서를 보장하려고 합니다.
(1)의 결괏값을 받고 나서 이후의 동작((2))을 수행하죠.

하지만 그렇다고 제어권을 계속 함수가 갖지는 않습니다.
(1)에서 기다리는 동안 바깥의 코드들이 실행되는 것을 확인할 수 있습니다.

promise((3))는 콜백을 이용하기 때문에 함수 내부의 코드((4))가 바로 동작하는 것을 실행 결과로 확인할 수 있습니다.
오해의 소지 없이 비동기/논블로킹으로 동작하죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const timer = (wait, value) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(value), wait);
  });

(async () => {
  // (1)
  const result = await timer(1000, "aaa");

  // (2)
  console.log(result);

  console.log("내부 실행 async await");
})();

console.log("외부 실행 1");

(() => {
  // (3)
  timer(1000, "bbb").then(console.log);
  
  // (4)
  console.log("내부 실행 promise");
})();

console.log("외부 실행 2");

/**
 * :: 실행 결과 ::
 * 
 * "외부 실행 1"
 * "내부 실행 promise"
 * "외부 실행 2"
 * "aaa"
 * "내부 실행 async await"
 * "bbb"
 */

😶‍🌫️ 마무리

솔직히 아직 동기와 블로킹 / 비동기와 논블로킹의 확실한 차이를 이해하지는 못했습니다.
코드상으로 증명되는 게 아닌 이론적인 개념이라고 느껴져서 확실하게 구분되는 동기 & 블로킹비동기 & 논블로킹에 대한 개념만 이해했습니다.
이후에 조금 더 공부해보면서 명확하게 차이가 이거고 어떤 예시를 생각해낼 수 있다면 그때 추가적으로 정리해보도록 하겠습니다.

📮 레퍼런스

  1. Youtube - Blocking vs Non-Blocking, Sync vs Async
  2. inpa - 동기 & 비동기 / 블로킹 & 논블로킹
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.