Language/Javascript

Node.js의 Libuv 라이브러리

JavaCPP 2025. 2. 26. 21:07

Libuv

Libuv 는 이벤트 루프를 기반으로 비동기 I/O를 지원하는 다중 플랫폼 C 라이브러리입니다 .

 

libuv는 원래 Node.js를 위해 작성된 크로스 플랫폼 지원 라이브러리입니다 . 이벤트 기반 비동기 I/O 모델을 중심으로 설계되었습니다.라이브러리는 다양한 I/O 폴링 메커니즘에 대한 간단한 추상화보다 훨씬 더 많은 것을 제공합니다. '핸들'과 '스트림'은 소켓과 다른 엔터티에 대한 고수준의 추상화를 제공합니다. 또한, 특히 크로스 플랫폼 파일 I/O 및 스레딩 기능도 제공됩니다.

 

다음은 libuv를 구성하는 다양한 부분과 각 부분이 관련된 하위 시스템을 보여주는 다이어그램입니다.

비동기 작업에 중요한 libuv 라이브러리입니다. libuv는 C++로 작성된, Node.js가 사용하는 비동기 I/O라이브러리로 비동기 작업이 어떤 커널이 지원하는지 확인합니다.

우리가 코드를 작성하고 코드를 실행한다면 Node.js내부적으로는 이러한 일이 일어납니다.

  1. js로 코드를 작성하고 실행하게 되면 스택에 코드가 쌓입니다.
  2. 이때 스택에 쌓인 코드를 실행하게 되면 libuv를 호출합니다.
  3. libuv는 비동기 처리를 할지 , 동기 처리를 할지 검사 후, 시스템 API를 이용하거나 쓰레드 풀에 생성된 쓰레드에게 작업을 위임합니다.
  4. 작업이 완료되면 콜백 함수를 테스크 큐에 넘겨줍니다.
  5. 이벤트 루프는 콜스택에 쌓여있는 함수가 없을 때, 테스크 큐에 대기하고 있던 콜백함수를 콜스택으로 넘겨줍니다.
  6. 콜스택에 쌓인 콜백 함수가 실행되고, 콜스택에서 제거됩니다

핸들 및 요청 

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 작업을 읽거나 쓰거나 수행할 수 있습니다.

 

다음 다이어그램은 루프 반복의 모든 단계를 보여줍니다.

  1. 'now'이라는 루프 개념이 초기에 설정됩니다.
  2. 루프가 UV_RUN_DEFAULT 로 실행된 경우 Due 타이머가 실행됩니다 . 루프의 now 개념(현재 시점)보다 먼저 예약된 모든 활성 타이머는 콜백을 호출합니다.
  3. 루프가 살아 있으면 반복이 시작되고, 그렇지 않으면 루프가 즉시 종료됩니다. 그렇다면 루프가 살아 있다고 간주되는 시점은 언제일까요 ? 루프에 활성 및 참조 핸들, 활성 요청 또는 닫는 핸들이 있으면 살아 있다고 간주됩니다 .
  4. 보류 중인 콜백이 호출됩니다. 모든 I/O 콜백은 대부분 I/O 폴링 직후에 호출됩니다. 그러나 이러한 콜백 호출이 다음 루프 반복으로 연기되는 경우가 있습니다. 이전 반복에서 I/O 콜백을 연기한 경우 이 지점에서 실행됩니다.
  5. Idle handle  콜백이 호출됩니다. 불행한 이름에도 불구하고 유휴 핸들은 활성화되어 있으면 모든 루프 반복에서 실행됩니다.
  6. Prepare 핸들 콜백이 호출됩니다. Prepare 핸들은 루프가 I/O를 위해 차단되기 바로 전에 콜백을 호출받습니다.
  7. 폴 타임아웃이 계산됩니다. I/O를 차단하기 전에 루프는 얼마나 오랫동안 차단해야 하는지 계산합니다. 타임아웃을 계산할 때의 규칙은 다음과 같습니다.
    • 루프가 UV_RUN_NOWAIT플래그와 함께 실행된 경우 시간 초과는 0입니다.
    • 루프가 중지될 경우( uv_stop()호출됨) 시간 초과는 0입니다.
    • 활성 핸들이나 요청이 없으면 시간 초과는 0입니다.
    • 활성화된 유휴 핸들이 있는 경우 시간 초과는 0입니다.
    • 닫혀야 하는 핸들이 있으면 시간 초과는 0입니다.
    • 위의 경우에 어느 것도 일치하지 않으면 가장 가까운 타이머의 시간 초과가 적용되고, 활성 타이머가 없으면 무한대가 적용됩니다.
  8. 루프는 I/O를 위해 차단합니다. 이 시점에서 루프는 이전 단계에서 계산된 기간 동안 I/O를 위해 차단됩니다. 읽기 또는 쓰기 작업을 위해 주어진 파일 설명자를 모니터링하던 모든 I/O 관련 핸들은 이 시점에서 콜백을 호출합니다.
  9. 체크 핸들 콜백이 호출됩니다. 체크 핸들은 루프가 I/O를 위해 차단된 직후에 콜백을 호출받습니다. 체크 핸들은 본질적으로 준비 핸들의 대응물입니다.
  10. 닫기 콜백이 호출됩니다. 핸들이 호출로 닫히면 uv_close()닫기 콜백이 호출됩니다.
  11. 'now'의 루프 개념(현재 시점)이 업데이트되었습니다.
  12. 만기 타이머가 실행됩니다. '지금'은 다음 루프 반복까지 다시 업데이트되지 않습니다. 따라서 다른 타이머가 처리되는 동안 타이머가 만기되면 다음 이벤트 루프 반복까지 실행되지 않습니다.
  13. 반복이 끝납니다. 루프가 UV_RUN_NOWAIT또는 UV_RUN_ONCE모드로 실행된 경우 반복이 끝나고 uv_run()돌아갑니다. 루프가 또는 모드로 실행된 경우 아직 살아UV_RUN_DEFAULT 있으면 처음부터 계속되고 , 그렇지 않으면 끝납니다.