Javascript study

[JS] 동기와 비동기, 블로킹과 논블로킹

카누가 좋아요 2023. 9. 8. 01:27

📌 참고 사이트

👩‍💻 완벽히 이해하는 동기/비동기 & 블로킹/논블로킹 (tistory.com)

 

👩‍💻 완벽히 이해하는 동기/비동기 & 블로킹/논블로킹

동기/비동기 & 블로킹/논블록킹 프로그래밍에서 웹 서버 혹은 입출력(I/O)을 다루다 보면 동기/비동기 & 블로킹/논블로킹 이러한 용어들을 접해본 경험이 한번 쯤은 있을 것이다. 대부분 사람들은

inpa.tistory.com

 

 

 

📋 동기와 비동기

📍 동기 (Synchronous)

동기를 영단어로 의미 파악을 해보면 syn(함께) + chrono(시간) 으로 작업 시간을 함께 맞추어 실행한다는 뜻으로 받아들일 수 있다.

이 뜻은 요청한 작업에 대한 완료 여부를 따져서 순차대로 처리하는 것을 말한다.

 

💡 요청한 작업에 대한 완료 알림을 반드시 받아야 다음 작업 수행이 가능하다. == 작업을 순서대로 처리한다.

 

 

📍 비동기 (Asynchronous)

동기의 영단어의 앞부분에 a를 붙여 부정의 의미를 가진다.

즉, 요청한 작업에 대한 완료 여부를 따지지 않고 자신의 다음 작업을 이어나가는 것을 말한다.

 

💡 요청한 작업에 대한 완료 알림이 중요하지 않다. == 작업의 순서가 지켜지지 않을 수 있다.

 

출처: https://velog.velcdn.com/images/brian_kim/post/2154f345-e488-444d-919f-3c025e9908cd/image.png

 

위 그림을 보면 동기에서는 클라이언트가 서버로부터 응답을 받을 때까지 다음 작업을 이어나가지 않는데, 비동기는 그 반대로 서버에 요청을 해놓고 응답을 기다리지 않고 클라이언트가 바로 다음 작업을 이어나가는 것을 볼 수 있다.

 

💡 비동기의 장점

요청한 작업에 대한 완료 여부를 신경쓰지 않고 다음 작업을 이어나가므로 I/O (입출력) 작업과 같은 느린 작업이 발생할 때, 그 작업이 완료되기를 기다리지 않고 다른 작업을 동시에 처리하여 전반적인 시스템 성능 향상에 도움을 줄 수 있다.

 

여기에서 '동시 처리' 란, 두 개 이상의 작업이 동시에 실행되는 것을 말한다.

이는 멀티 스레드 등과 같은 방식으로 구현이 가능한데, JS는 싱글 스레드 언어이지만 비동기로 작업을 요청할 경우 브라우저에 내장된 멀티 스레드로 이루어진 Web API에 작업이 인가되어 Call Stack과 동시에 작업이 처리되게 되므로 동시 처리가 가능하다.

 

비동기 작업 ex) 애니메이션 실행, 네트워크 통신, 마우스 키보드 입력, 타이머 등등

❗ JS 코드 실행 자체는 Web API가 아닌 Call Stack에서 실행됨을 명심하자!

(ex) setTimeout을 사용했을 때 Web API에서는 코드 실행이 아닌 타이머를 돌려주는 역할만 한다.)

 

동기적 작업보다 더 빠른 시간 안에 작업을 끝내는 비동기적 작업 (출처: https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https:%2F%2Fblog.kakaocdn.net%2Fdn%2FcHDjOh%2FbtrKBFXHPAJ%2F9vbmWGksa5sF3gcrUZK6l1%2Fimg.png)

 

 

 

📋 블로킹과 논블로킹

동기와 비동기 전체적인 작업에 대한 순차적인 흐름 유무라면, 블로킹과 논블로킹 전체적인 작업의 흐름 자체를 막냐 막지 않냐로 볼 수 있다.

ex) 블로킹 방식으로 읽으면 파일을 다 읽을 때까지 대기하고, 논블로킹 방식으로 읽으면 파일을 다 읽지 않아도 다른 작업을 하는 것이 가능하다.

 

❓ 제어권

동기와 비동기, 블로킹과 논블로킹을 명확하게 구분하기 위해 '제어권' 개념을 사용하기도 한다.

제어권 함수의 코드나 프로세스의 실행 흐름을 제어할 수 있는 권리이다.

즉, 호출된 함수가 호출한 함수에게 제어권을 바로 주느냐 주지 않느냐블로킹과 논블로킹이 구분되고, 제어권이 호출한 함수로 넘어가 버리면 해당 스레드는 블로킹되게 되는 것이다.

 

📍 블로킹 (Blocking)

출처: https://velog.velcdn.com/images%2Fnittre%2Fpost%2F8cdc0a02-d469-47d5-96c8-f6aeef204eb7%2Fimage.png

 

1️⃣ A 함수가 B 함수를 호출하면 B 함수에게 제어권이 넘어간다.

2️⃣ 제어권을 넘겨받은 B 함수는 함수를 실행한다.

3️⃣ 이때, A 함수는 제어권을 B에게 넘겼기 때문에 실행을 잠시 멈춘다. 이때 Blocking이 일어났다고 한다.

4️⃣ B 함수의 실행이 끝나면 자신을 호출한 A 함수에게 제어권을 돌려준다.

5️⃣ 제어권을 다시 받은 A 함수는 자신의 그다음 작업을 이어나간다.

 

 

📍 논블로킹 (Non-Blocking)

출처: https://velog.velcdn.com/images%2Fnittre%2Fpost%2Fc839fc04-1788-4063-ab38-b0d4a312dbf4%2Fimage.png

 

1️⃣ A 함수가 B 함수를 호출한다.

2️⃣ 호출된 B 함수는 실행되지만, 제어권은 A 함수가 그대로 가지고 있는다. 따라서 논블로킹이다.

3️⃣ A 함수는 계속 제어권을 가지고 있으므로 B 함수를 호출한 이후에도 자신의 코드를 계속 실행한다.

 

 

 

📋 동기 / 비동기 + 블로킹 / 논블로킹  

📍 동기 (Sync) + 블로킹 (Blocking) 조합

출처: https://velog.velcdn.com/images%2Fnittre%2Fpost%2Ff6212fee-ee42-4023-ae02-d2dc15eec46a%2Fimage.png

 

다른 작업이 진행되는 동안 자신의 작업을 처리하지 않고(Blocking), 다른 작업의 완료 여부를 바로 받아 순차적으로 처리(Sync)하는 방식을 말한다.

 

💻 ex) 동기적으로 파일을 읽어 내용을 처리하는 로직

 

// 파일 시스템 모듈 불러오기
const fs = require('fs');  

// 동기적으로 파일 읽기 (readFileSync, 코드가 순차적으로 실행됨.)
const data1 = fs.readFileSync('file1.txt', 'utf8');
console.log(data1);    

const data2 = fs.readFileSync('file2.txt', 'utf8');
console.log(data2);

const data3 = fs.readFileSync('file3.txt', 'utf8');
console.log(data3);

 

결과적으로 위 코드는 순차적으로 실행되어 data1, data2, data3에 관한 내용이 차례로 콘솔 창에 찍히게 된다.

동기 + 블로킹 방식은 위처럼 작은 데이터를 처리하거나 파일 하나를 읽고 쓰는 경우 간단하게 사용할 수 있지만 작업량이 많아지면 비효율적이게 된다.

 

 

📍 비동기 (Async) + 논블로킹 (Non-Blocking) 조합

출처: https://velog.velcdn.com/images%2Fnittre%2Fpost%2Fb9566928-9a6b-4111-9cad-528daa45475d%2Fimage.png

 

다른 작업이 진행되는 동안에도 자신의 작업을 처리(Non-Blocking)하고 다른 작업의 결과를 바로 처리하지 않아 작업 순서가 지켜지지 않는(Async) 방식이다.

다른 작업의 결과가 자신의 작업에 영향을 주지 않는 경우에 활용 가능하다.

 

💻 ex) 비동기적으로 파일을 읽어 내용을 처리하는 로직

위에서 본 동기적으로 파일을 읽어 내용을 처리하는 로직을 그대로 비동기 방식으로 바꾼 것이다.

 

// 파일 시스템 모듈 불러오기
const fs = require('fs');

// 비동기적으로 파일 읽기 (readFile)
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;     // 에러 처리
  console.log(data);     // 파일 내용 출력
});

fs.readFile('file2.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

fs.readFile('file3.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// 작업 완료 메시지
console.log('done');

 

동기 + 블로킹 방식에서와 다른 점은 호출 함수에 콜백 함수를 넣어 비동기 + 논블로킹 방식대로 처리된 작업의 결과의 후처리를 가능하게 하였다.

위 코드는 비동기이기 때문에 작업의 순서를 고려하지 않아(완료 여부를 받지 않음) 결과적으로 'done'이 콘솔에 가장 먼저 찍히고, 그 후에 파일 내용이 출력되게 된다.

 

비동기 + 논블로킹 방식은 작업량이 많거나 시간이 오래 걸리는 작업을 처리해야 하는 경우 한 작업 처리 동안 다른 작업 처리가 가능하므로 전체 처리 시간을 줄일 수 있어 유리하다.

 

 

📍 동기(Sync) + 논블로킹 (Non-Blocking) 조합

출처: https://velog.velcdn.com/images%2Fnittre%2Fpost%2Ffe5d1231-4c3c-4caf-bdd8-2287926e38ca%2Fimage.png

 

다른 작업이 진행되는 동안에도 자신의 작업을 처리하고 (Non-Blocking), 다른 작업의 결과를 바로 처리하여 작업을 순차대로 수행(Sync)하는 방식이다. 동기 작업이기 때문에 다른 작업의 완료 여부를 받아야 하므로 계속 작업이 완료되었는지 확인한다. (위 그림 참고)

 

💻 ex) Java에서의 스레드 객체 생성 후 처리 

스레드(Thread) 객체를 만들어 요청 작업을 백그라운드에 돌게 하고, 메인 메서드에서 while 문을 통해 스레드가 모두 처리되었는지 끊임없이 확인하고, 처리가 완료되면 다음 메인 작업을 수행한다.

 

💻 ex) JS의 async / await 키워드를 이용해 동기 + 논블로킹 코드 비슷하게 구현하기

JS에서 제공하는 메서드로 동기 + 논블로킹 코드를 완벽히 구현할 수는 없다.

세 개의 파일을 동시에 읽기 시작하고(Non-Blocking, Promise.all), 파일 읽기가 모두 완료되어 변수에 내용이 담기면(await) 내용을 비교하는 후처리를 진행(Sync)하는 식으로 코드를 작성하면 된다.

 

const fs = require('fs');
const {promisify} = require('util');     // 유틸리티 모듈 가져오기
const readFileAsync = promisify(fs.readFile);     // fs.readFile 함수를 Promise 객체를 반환하는 함수로 변환

async function readFiles() {
  try {
    // Promise.all() 메서드로 여래 개의 비동기 작업을 병렬로 처리 (비동기 논블로킹)
    const [data1, data2, data3] = await Promise.all([
      readFileSync('file.txt', 'utf8'),     // file.txt 파일 읽기
      readFileSync('file.txt', 'utf8'),     // file2.txt 파일 읽기
      readFileSync('file3.txt', 'utf8')     // file3.txt 파일 읽기
    ]);
    
    // 세 파일을 읽는 것이 모두 완료되면 각각의 data에 파일 내용이 들어간다.
    console.log(data1);     // file.txt 파일 내용
    console.log(data2);     // file2.txt 파일 내용
    console.log(data3);     // file3.txt 파일 내용
    
    // 파일 비교 로직 실행...
  
  } catch (err) {
    throw err;
  }
}

readFiles();     // async 함수 호출

 

❗ async / await 의 '내부' 적으로는 여전히 비동기 논블로킹 방식으로 동작한다. async 함수 자체가 Call Stack이 모두 실행되어 비워져야 작업이 실행되기 때문이다.

 

 

📍 비동기 (Async) + 블로킹 (Blocking) 조합

출처: https://velog.velcdn.com/images%2Fnittre%2Fpost%2F9b6754f0-8721-4308-8a62-d884c7315d15%2Fimage.png

 

다른 작업이 진행되는 동안 자신의 작업을 멈추고 기다리면서(Blocking), 다른 작업의 결과를 바로 처리하지 않아 순서대로 작업을 수행하지 않는 (Async) 방식이다.

사실 마주할 일이 거의 없는 경우이다.

위 그림을 보면 A 함수는 B 함수의 결과에 관심이 없음에도 불구하고 B 함수에게 제어권을 넘겨 실행을 멈추고 있다.