Node.js의 Libuv 라이브러리
Libuv
Libuv 는 이벤트 루프를 기반으로 비동기 I/O를 지원하는 다중 플랫폼 C 라이브러리입니다 .
libuv는 원래 Node.js를 위해 작성된 크로스 플랫폼 지원 라이브러리입니다 . 이벤트 기반 비동기 I/O 모델을 중심으로 설계되었습니다.라이브러리는 다양한 I/O 폴링 메커니즘에 대한 간단한 추상화보다 훨씬 더 많은 것을 제공합니다. '핸들'과 '스트림'은 소켓과 다른 엔터티에 대한 고수준의 추상화를 제공합니다. 또한, 특히 크로스 플랫폼 파일 I/O 및 스레딩 기능도 제공됩니다.
다음은 libuv를 구성하는 다양한 부분과 각 부분이 관련된 하위 시스템을 보여주는 다이어그램입니다.
비동기 작업에 중요한 libuv 라이브러리입니다. libuv는 C++로 작성된, Node.js가 사용하는 비동기 I/O라이브러리로 비동기 작업이 어떤 커널이 지원하는지 확인합니다.
우리가 코드를 작성하고 코드를 실행한다면 Node.js내부적으로는 이러한 일이 일어납니다.
- js로 코드를 작성하고 실행하게 되면 스택에 코드가 쌓입니다.
- 이때 스택에 쌓인 코드를 실행하게 되면 libuv를 호출합니다.
- libuv는 비동기 처리를 할지 , 동기 처리를 할지 검사 후, 시스템 API를 이용하거나 쓰레드 풀에 생성된 쓰레드에게 작업을 위임합니다.
- 작업이 완료되면 콜백 함수를 테스크 큐에 넘겨줍니다.
- 이벤트 루프는 콜스택에 쌓여있는 함수가 없을 때, 테스크 큐에 대기하고 있던 콜백함수를 콜스택으로 넘겨줍니다.
- 콜스택에 쌓인 콜백 함수가 실행되고, 콜스택에서 제거됩니다
핸들 및 요청
libuv는 이벤트 루프와 함께 사용할 수 있는 핸들과 요청이라는 두 가지 추상화를 사용자에게 제공합니다.
핸들은 활성 상태에서 특정 작업을 수행할 수 있는 장기적인 객체를 나타냅니다.
몇 가지 예:
- prepare handle은 활성화되어 있을 때 루프 반복마다 한 번씩 콜백을 호출합니다.
- 새로운 연결이 있을 때마다 연결 콜백이 호출되는 TCP 서버 핸들입니다.
요청은 (일반적으로) 단기간 작업을 나타냅니다. 이러한 작업은 핸들을 통해 수행할 수 있습니다. 쓰기 요청은 핸들에 데이터를 쓰는 데 사용됩니다. 또는 단독으로: getaddrinfo 요청은 핸들이 필요 없고 루프에서 직접 실행합니다.
I/O(이벤트) 루프
I/O(또는 이벤트) 루프는 libuv의 핵심 부분입니다. 모든 I/O 작업에 대한 콘텐츠를 설정하고 단일 스레드에 연결되도록 되어 있습니다. 각각이 다른 스레드에서 실행되는 한 여러 이벤트 루프를 실행할 수 있습니다. libuv 이벤트 루프(또는 루프나 핸들과 관련된 다른 API)는 달리 명시된 경우를 제외하고는 Thred-Safe 하지 않습니다 .
Thred-Safe: 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다.
이벤트 루프는 다소 일반적인 단일 스레드 비동기 I/O 접근 방식을 따릅니다. 모든 (네트워크) I/O는 주어진 플랫폼에서 사용 가능한 최상의 메커니즘을 사용하여 폴링되는 비차단 소켓에서 수행됩니다. (Linux의 epoll, OSX 및 기타 BSD의 kqueue, SunOS의 이벤트 포트 및 Windows의 IOCP) 루프 반복의 일부로 루프는 폴러에 추가된 소켓에서 I/O 활동을 기다리며 차단되고 콜백이 실행되어 소켓 조건(읽기 가능, 쓰기 가능 hangup)을 나타내므로 핸들이 원하는 I/O 작업을 읽거나 쓰거나 수행할 수 있습니다.
다음 다이어그램은 루프 반복의 모든 단계를 보여줍니다.
- 'now'이라는 루프 개념이 초기에 설정됩니다.
- 루프가 UV_RUN_DEFAULT 로 실행된 경우 Due 타이머가 실행됩니다 . 루프의 now 개념(현재 시점)보다 먼저 예약된 모든 활성 타이머는 콜백을 호출합니다.
- 루프가 살아 있으면 반복이 시작되고, 그렇지 않으면 루프가 즉시 종료됩니다. 그렇다면 루프가 살아 있다고 간주되는 시점은 언제일까요 ? 루프에 활성 및 참조 핸들, 활성 요청 또는 닫는 핸들이 있으면 살아 있다고 간주됩니다 .
- 보류 중인 콜백이 호출됩니다. 모든 I/O 콜백은 대부분 I/O 폴링 직후에 호출됩니다. 그러나 이러한 콜백 호출이 다음 루프 반복으로 연기되는 경우가 있습니다. 이전 반복에서 I/O 콜백을 연기한 경우 이 지점에서 실행됩니다.
- Idle handle 콜백이 호출됩니다. 불행한 이름에도 불구하고 유휴 핸들은 활성화되어 있으면 모든 루프 반복에서 실행됩니다.
- Prepare 핸들 콜백이 호출됩니다. Prepare 핸들은 루프가 I/O를 위해 차단되기 바로 전에 콜백을 호출받습니다.
- 폴 타임아웃이 계산됩니다. I/O를 차단하기 전에 루프는 얼마나 오랫동안 차단해야 하는지 계산합니다. 타임아웃을 계산할 때의 규칙은 다음과 같습니다.
- 루프가 UV_RUN_NOWAIT플래그와 함께 실행된 경우 시간 초과는 0입니다.
- 루프가 중지될 경우( uv_stop()호출됨) 시간 초과는 0입니다.
- 활성 핸들이나 요청이 없으면 시간 초과는 0입니다.
- 활성화된 유휴 핸들이 있는 경우 시간 초과는 0입니다.
- 닫혀야 하는 핸들이 있으면 시간 초과는 0입니다.
- 위의 경우에 어느 것도 일치하지 않으면 가장 가까운 타이머의 시간 초과가 적용되고, 활성 타이머가 없으면 무한대가 적용됩니다.
- 루프는 I/O를 위해 차단합니다. 이 시점에서 루프는 이전 단계에서 계산된 기간 동안 I/O를 위해 차단됩니다. 읽기 또는 쓰기 작업을 위해 주어진 파일 설명자를 모니터링하던 모든 I/O 관련 핸들은 이 시점에서 콜백을 호출합니다.
- 체크 핸들 콜백이 호출됩니다. 체크 핸들은 루프가 I/O를 위해 차단된 직후에 콜백을 호출받습니다. 체크 핸들은 본질적으로 준비 핸들의 대응물입니다.
- 닫기 콜백이 호출됩니다. 핸들이 호출로 닫히면 uv_close()닫기 콜백이 호출됩니다.
- 'now'의 루프 개념(현재 시점)이 업데이트되었습니다.
- 만기 타이머가 실행됩니다. '지금'은 다음 루프 반복까지 다시 업데이트되지 않습니다. 따라서 다른 타이머가 처리되는 동안 타이머가 만기되면 다음 이벤트 루프 반복까지 실행되지 않습니다.
- 반복이 끝납니다. 루프가 UV_RUN_NOWAIT또는 UV_RUN_ONCE모드로 실행된 경우 반복이 끝나고 uv_run()돌아갑니다. 루프가 또는 모드로 실행된 경우 아직 살아UV_RUN_DEFAULT 있으면 처음부터 계속되고 , 그렇지 않으면 끝납니다.