본문 바로가기
언어 이야기/Java Script

async/await은 blocking이 아닐까? (비동기 처리 고찰)

by 천릉객 2023. 9. 21.

1. 의문이 든건 아래의 코드에서부터.

router.get('/', async (req, res) => {
   const datas = await books.find({})
   res.json(datas)
})

books.find({})를 통해 books에서 모든 데이터를 가져오는걸 "기다렸다가" json으로 보내라는 코드이다

기다렸다가...?

node.js는 싱글스레드 기반이니까 blocking인가보다.

2. 근데 js는 싱글 스레드 + 논블락킹이라며?

별 생각 없이 넘어가려했는데.... 흔들리지 않는 개념이 있었다. "js는 싱글스레드 + 논블락킹"이라는 것.

근데 저건 blocking아닌가?

찾다보니 하나의 글을 읽을 수 있었다.

https://medium.com/geekculture/does-async-await-block-javascript-main-thread-c07db9c48c3e

 

Does async await block JavaScript Main thread ?

If you have landed on this page, most likely you know what is async and await and at some point in time you might have got this question in your mind are they really blocking JavaScript main thread …

medium.com

중요한 부분만 가져오자면

요약하자면

"Hello world"가 먼저 출력되고 "After all promise is executed"가 나중에 출력된다는 것.

이는 await이 메인 스레드를 차단하지 않음을 보여준다는 것.

내 공부가 부족한게 뻔하다. 본질로 돌아가자.

3. 결국 async/await는 Promise를 쉽게 쓰기 위함이 아니던가?

바로 내 문제점이 나왔다.

나는 Promise를 잘 모른다....

4. Promise 복습

  • 프로미스란?
    • 자바스크립트 비동기 처리에 사용되는 객체
    • 콜백지옥을 해결하기 위해 도입
    • 비동기 작업이 완료되면, 콜백으로 해결하던 다음 작업을 then으로 처리
  • 프로미스 객체
    • resolve 인자 : 요청이 성공할 경우 호출할 메서드
    • reject : 요청이 실패할 경우 호출할 메서드
  • 프로미스 처리
    • .then() : 프로미스 객체 내부에서 resolve를 호출할 경우, 이 때 호출된 resolve의 매개변수가 then의 인자로 들어가서 사용
    • .catch() : 실패해서 reject가 불릴 경우, catch로 인자를 받아 에러처리
  • 프로미스 상태
    • pending : 처리 중
    • fulfilled : 처리 완료
    • ejected : 실패
  • 프로미스 체이닝
    • .then을 연속적으로 사용
    • 이전 then 에서 return된 값을 그 다음 then의 인자로 사용
    • 여러개의 비동기 작업을 순차적으로 수행할 수 있음

5. 그렇다면 내가 async/await을 blocking으로 착각했던 이유는 뭘까?

결국 await은 프로미스 체이닝을 구현한 것이고, 맨 위에 있는 코드의 res.json(datas)의 경우 실행이 "block" 된게 아니라 await의 리턴을 "기다리는"것이다. 즉 프로미스에서의 then으로 해석하는 것이 맞다. 하지만 프로미스와 달리 then이 써있지 않기 때문에 오해했던 것.

만약 그 뒤에 오는 코드가 await과 관련이 없었다면 바로 진행이 됐을것이다.

6. 남아있는 의문, 그러면 싱글스레드인데 find({})의 처리는 누가하나?

출처 : https://velog.io/@jguuun/jsEventLoop

내가 알고있는 이벤트루프의 아키텍쳐가 딱 위의 그림이다.

근데 내가 모르는 것이 있었다.

7. 백그라운드 스레드 풀의 존재

https://snyk.io/blog/node-js-multithreading-worker-threads-pros-cons/ 

 

Node.js multithreading with worker threads: pros and cons | Snyk

In this article, we'll look at the pitfalls of worker threads and how they differ from the multithreading implementations in other programming languages.

snyk.io

위 글을 참고했다.

나에게 필요한 부분을 정리하면 이렇다.

  • 백 그라운드 스레드 풀에서는 워커 스레드가 동작한다.
  • 주 프로세스는 싱글 스레드이며 멀티 스레드라면 "동시에 동일 한 상태 (프로세스의 메모리 등) "를 공유해야하지만 워커 스레드의 경우 node의 런타임과 격리된 인스턴스를 생성해서 동작하며, 메인 프로세스와는 메시지 패싱을 통해 데이터를 주고 받는다.

8. 추가적인 궁금증이 생겼다. 왜 굳이 이렇게 하나?

그냥 멀티 스레드를 쓰지. 굳이 왜?

이에 대해서는 멘토(코치)님께서 답이 될만한 좋은 글을 하나 보여주셨다.

https://webhostinggeeks.com/blog/c10k-problem-understanding-and-overcoming-the-10000-concurrent-connections-challenge/

 

C10K Problem: Understanding and Overcoming the 10,000 Concurrent Connections Challenge | Web Hosting Geeks' Blog

Explore the world of server performance as we delve into the C10K problem – the challenge of handling 10,000 concurrent connections. Learn its origins, impacts, and the evolution of solutions from thread-based models to event-driven architectures.

webhostinggeeks.com

"10,000개의 요청이 들어왔을때 어떻게 처리할 것인가?" 에 대한 글이다. (해석해보려다가 너무 길어서 중간부터 gpt 돌렸다 ㅠㅠ)

짧게 요약하자면 이렇다

  • 트래픽이 늘어감에 따라 많은 오버헤드 발생 및 처리 능력 필요성 대두
  • 웹 서버의 처리능력에는 다양한 요소 개입 (하드웨어 사양, 서버 아키텍쳐 종류 등)
  • 여러가지 해결책 존재
    • 이벤트 기반 프로그래밍
    • IO 작업 다중화
    • 스레드 풀링
    • 네트워크, OS 개선
    • 분산처리 (현대의 방식)

내 질문에 대한 답변은 "이벤트 기반 프로그래밍"에서 찾을 수 있었다.

이벤트 기반 프로그래밍에서는 각 연결에 대해 스레드를 유지하는 대신, 이벤트가 발생할 때 까지 "대기"상태에 있으며 이벤트가 발생할 경우 적절히 대응한 다는 것.

그리고 그러한 방식을 사용하는 "싱글 스레드"로 다루는 Nginx의 경우 더 효율적인 자원 사용을 가져온다는 것.

또한 스레드 풀은 스레드를 생성과 소멸하는 대신 재사용 해서 오버헤드를 감소시킨다는 것.

 

즉, 위의 내용을 잘 버무리면 "여러 개의 스레드를 운영하는 것은 오버헤드를 초래하며, 이를 잘 관리하는 것이 곧 성능을 향상시키는 것" 이라는 결론을 얻을 수 있다. 또한 이는 트래픽이 과부하될 때 더 두드러지는 장점으로 볼 수있다.

 

물론 현재의 서버컴퓨터는 분명 많은 코어로 높은 멀티 스레딩 성능을 가질 것이기 때문에, 꼭 어떤것이 정답이라고 볼 수는 없지만. 싱글 스레드 기반으로 이벤트 처리를 잘 하는것은 분명히 메리트가 있음은 확실하다.

node.js와 nginx의 점유율이 그 근거일테고.

9. 마치며

어렵다. 갈길이 멀다.

쩝...