나의 첫 개발 프로젝트인 '슈퍼슬랩'을 진행하면서 공부하고 싶은 것들이 정말 많았다. 프로젝트를 진행하면서 같이 공부하는 것이 베스트였겠지만, 5주라는(심지어 기획, 발표 준비 등까지 하면 3주도 안됐음) 짧은 기간 동안 개념을 깊게 이해하면서 서비스를 완성시키는데 제한이 있었다.
이제 프로젝트 발표도 끝났겠다, 공부할 시간도 생겼겠다, 그동안 깊게 파지 못 했던 부분들을 한 번 정리해보자!!
▼ 프로젝트 구경하러 가기!
지인들과 내기할 때 사용하는 웹기반 내기용 게임 슈퍼슬랩(SuperSlap)
1. 자바스크립트의 동작원리
자바스크립트는 싱글 스레드 기반이고 내부의 event loop는 비동기적으로 메시지를 처리한다! 프로젝트를 진행할 때 이 정도만 이해했다. 지금부터는 더 깊게, 디테일하게 궁금한 점들을 공부해보려고 한다.
OS를 공부하면서 프로세스 동기화와 상호배제에 대한 개념을 배웠었다. race condition 상황을 제어하기 위해 락, 세마포어, 모니터 등의 상호배제 기법을 공부했었다. 즉 멀티 스레드 방식에서 병행 수행 중인 프로세스들이 자원에 동시 접근할 때 문제가 발생(e.g. race condition)할 수 있기 때문에 동기화를 해야 한다고 인식하고 있었다.
▼ 병행vs병렬?
※ 병행(Concurrency)와 병렬(Parallelism)의 차이
병행은 소프트웨어적인 의미를 가진다. 프로세스들은 작업을 수행할 때 CPU 연산 과정을 거치는데, CPU가 하나의 프로세스를 작업하고 있다면 다른 프로세스들은 CPU를 점유할 수 없다. 이런 물리적인 상황을 해결하기 위해서 스케줄링 등을 통해 작업을 번갈아가면서 여러 프로세스를 교차 수행하게 한다. 교차 과정이 매우 짧은 시간에 여러 번 수행되므로 마치 여러 작업들이 동시에 실행되는 것 처럼 보인다.
병렬은 멀티 코어 환경에서 여러 개의 코어에 작업을 각 코어로 분산시켜서 동시에 작업하는 것을 의미하며 실제 물리적으로 여러 작업이 동시에 실행된다.
참고로 내 노트북 CPU는 Intel(R) Core(TM) i7-9750H로 6코어 12스레드임! 여기서 궁금한 점. 하나의 코어에서 하나의 스레드를 실행할 수 있다고 배웠는데 어떻게 6개의 코어에서 12개의 스레드를 작업하는거지...?
↓↓↓↓
최근에는 물리적으로 하나인 코어를 논리적으로 둘로 나눠 마치 전체 코어 수가 2배로 늘어난 것과 유사한 효과를 볼 수 있는 SMT(Simultaneous Multi-Threading) 기술이 적용된 CPU가 다수를 차지하고 있다. 인텔에선 자사 제품에 적용된 SMT 기술을 하이퍼쓰레딩(Hyper Threading)이라고 부른다. 이런 CPU는 1개의 코어당 2개의 쓰레드를 갖추게 된다.
멀티 스레드 방식을 사용하는 것이 합리적이라고 생각했는데 자바스크립트는 왜 비동기 처리를 하는걸까? 멀티 스레드 방식의 단점에 대해서 먼저 생각을 해봐야 할 것 같다.
멀티 스레드의 한계
멀티 스레드 방식은 서버의 요청 처리를 스레드에서 처리하도록 하여 병렬처리를 가능하도록 하는 방식이다. 스레드는 시분할시스템 방식을 통해 서버 CPU 자원을 여러 스레드들이 번갈아가면서 사용할 수 있게 한다.
▼ 시분할(Time Sharing)?
운영체제는 CPU를 가상화하여 CPU가 여러 개로 존재하는 것 같은 환상을 만들어 낸다. 하나의 프로세스를 실행하고, 이를 중단시키고 다른 프로세스를 실행하는 작업을 반복하면서 하나, 또는 소수의 CPU로 많은 CPU가 존재하는 것 처럼 한다. 이는 시분할이라는 기법을 통해 원하는 수 만큼의 프로세스를 실행할 수 있게 한다. 다만 시분할 기법은 CPU를 공유하기 때문에 이 CPU를 사용하는 프로세스들의 성능은 저하된다.
그럼 시분할이 뭐지? 시분할은 자원 공유를 운영체제가 사용하는 가장 기본 기법 중 하나이다. 한 개체가 잠깐 자원을 사용한 후, 다른 개체가 또 잠깐 자원을 사용하고, 그 다음 개체가 사용하면서 이 자원 (CPU 또는 네트워크 링크 등)을 많은 개체들이 공유한다.
시분할 하면 같이 나오는 키워드는 '라운드 로빈(Round Robin)'이다. RR 방식은 다음에 더 추가!
즉 멀티 스레드 기반의 서버는 클라이언트의 요청마다 스레드를 발생시키고, 만약 동시 접속자 수가 많다면 스레드도 많이 발생하며 서버의 자원은 한정적이기 때문에 일정 수 이상의 스레드가 생기면 컨텍스트스위치 오버헤드가 많이 발생될 것이고, 성능면에서 문제가 될 것이다(사용자 입장에서 반응속도가 엄청 느려진다던가!).
이런 문제를 해결하기 위해 성능이 좋은 서버로 업그레이드 하거나 로드밸런싱을 통해 분산처리를 한다고 한다. 이번에 슈퍼슬랩 프로젝트를 진행하면서 로드밸런싱도 도전해보고 싶었지만 당장 게임을 이용하는 사용자들의 피드백을 통한 개선이 우선이었기 때문에 아쉽게 로드밸런싱을 구현하지는 못했다. 다음에 기회될 때 공부해보자!
Anyway, 멀티 스레드에서 스레드간 공유자원을 이용할 때 동기화에 신경을 써야 한다는 것!
비동기 처리
동기방식 처리는 하나의 요청이 처리되는 동안 다른 요청이 처리되지 못하며 요청이 완료되어야만 다음 처리가 가능하다. 동기방식은 IO처리를 Blocking하는데 이 문제를 스레드로 처리했다.
이 문제를 비동기 방식으로 처리할 수도 있다. 비동기 방식은 하나의 요청처리가 완료되기 전에 제어권을 다음 요청으로 넘긴다. 따라서 IO처리인 경우 Blocking되지 않으며 다음 요청을 처리할 수 있다. 그리고 자바스크립트의 Node.js는 비동기 방식으로 병렬처리를 한다.
Node.js는 비동기IO를 지원하며 싱글 스레드 기반으로 동작하는 서버다. Node 서버는 비동기 방식으로 요청을 처리하면서 다음 요청을 받을 수 있다. 또한 병렬처리를 스레드로 처리하지 않으므로 멀티 스레드가 가지는 문제에서 자유롭다.
그러면 비동기 처리는 어디에서 이루어지는거지? 위의 그림을 보면 Event loop를 통해 가능하다. 클라이언트의 요청을 비동기로 처리하기 위하여 이벤트가 발생하면 서버 내부에 메시지 형태로 전달한다. 서버 내부에서는 이 메시지를 Event loop가 처리한다. 이벤트루프가 처리하는 동안 제어권은 다음 요청으로 넘어가고 처리가 완료되면 Callback을 호출하여 호출측에 알린다.
음... 뭔가 설명이 부실하게 느껴진다. 자바스크립트 엔진에 대해 조금 더 찾아보자.
자바스크립트 엔진의 주요 구성 요소는 'Memory Heap'과 'Call Stack'이 있다.
- Memory Heap: 메모리 할당이 일어나는 곳
- Call Stack: 코드 실행에 따라 호출 스택이 쌓이는 곳
즉, 위에서 Node.js의 비동기 처리를 할 때, 요청들은 콜스택에 쌓이게 되고, 요청에 대한 처리는 이벤트루프를 통해 처리한다는 것. 여기서 궁금한 것은 이벤트 루프는 어떤 방식으로 처리하냐는 것이다.
여기서부터 큐(queue)가 등장한다. 두둥...! 자바스크립트의 이벤트루프는 콜스택과 큐 사이의 작업들을 확인하고 콜스택이 비어있는 경우 큐의 작업을 꺼내어 콜스택에 쌓는다. 직접적인 작업은 wdbAPI에서 처리되고, 처리 완료되면 큐에 쌓는다. 이때 webAPI에서는 별개의 스레드가 작업을 실행한다. 그림으로 보면 다음과 같다.
와.. 팔수록 더 깊어진다;; Node.js 공부할 때부터 계속 봤던 V8엔진에 대해서도 알아야 할 것 같다. 이 부분은 오늘 우선적으로 공부하기로 했던 동기vs비동기, 블로킹vs논블로킹에 대한 공부 이후에 더 공부해보자.
2. 동기vs비동기, 블로킹vs논블로킹, 뭐가 다른거야?
동기, 비동기에 대해 공부를 하면서 헷갈리에 하는 개념. Blocking과 Non-Blocking. 일단 큰 흐름으로 이해한 것으로는 동기는 Blocking IO처리, 비동기는 Non-Blocking IO처리라는건데...
찾아본 바로는 위의 개념들을 이해하기 위해서는 먼저 '제어권'에 대한 용어를 짚고 넘어가야 한다고 한다.
- 제어권: 제어권은 자신(함수)의 코드를 실행할 권리를 말한다. 제어권을 가진 함수는 자신의 코드를 끝까지 실행한 후, 자신을 호출한 함수에게 돌려준다.
블로킹(Blocking) vs 논블로킹(Non-Blocking)
블로킹과 논블로킹은 제어권을 어떻게 처리하느냐에 따라 달라진다.
블로킹은 A함수가 B함수를 호출하면, 제어권을 A가 B에게 넘겨준다.
반면에 논블로킹은 A함수가 B함수를 호출해도 제어권은 그대로 자신이 가지고 있는다.
동기(Synchronous) vs 비동기(Asynchronous)
동기와 비동기의 차이는 호출되는 함수의 작업 완료 여부를 신경쓰는지의 여부의 차이이다.
함수 A가 함수 B를 호출한 뒤, 함수 B의 리턴값을 확인하면서 신경 쓰는 것이 동기, 함수 B의 작업 완료 여부를 신경쓰지 않으면 비동기.
흠... 여기서 말하는 '제어권'이라는 것이 명확하게 이해가 되지 않는다. 도대체 무엇이 제어권을 가지고 실행한다는거지?
프로세스의 상태를 보여주는 그림을 한 번 보자.
그림을 보면, CPU를 할당 받은 프로세스는 running상태가 된다. 그리고 IO 요청이 들어오면 sleep(block)상태로 가며 IO 처리가 끝나면 다시 ready상태로 가고, cpu를 할당받기 전까지 대기한다. 제어권이라는 것은 결국 CPU에서 실행되고 있냐를 나타내는건가? 하지만 이것만으로는 설명이 부족하다. 비동기로 처리되는 프로세스들도 다른 코어에 의해서 실행되고 있는 것일테니까. 아 싱글 스레드를 실행하는 서버에 대한 제어권만 바뀌지 않고, IO처리하는 것을 이벤트루프를 통해 다른 API에서 실행하는데, 이 때 다른 스레드가 병렬적으로 처리를 한다는 것인가. 더 공부를 해봐야겠다ㅠㅠ!
'개발자 도전기 > [STUDY] JS || TS' 카테고리의 다른 글
JavaScript | FP && ES6+ | go, pipe, curry (0) | 2022.01.27 |
---|---|
JavaScript | FP && ES6+ | map, filter, reduce (0) | 2022.01.25 |
JavaScript | FP && ES6+ | 제너레이터와 이터레이터 (0) | 2022.01.21 |
JavaScript | FP && ES6+ | ES6에서의 순회와 이터러블:이터레이터 프로토콜 (0) | 2022.01.20 |
JavaScript | FP && ES6+ | 함수형 자바스크립트 기본기 (0) | 2022.01.19 |
댓글