[libuv] libuv의 특징

lsh235·2024년 11월 14일
0

libuv

목록 보기
2/5

지원 기능

epoll, kqueue, IOCP, 이벤트 포트로 지원되는 모든 기능을 갖춘 이벤트 루프입니다.
비동기 TCP 및 UDP 소켓
비동기 DNS 확인
비동기 파일 및 파일 시스템 작업
파일 시스템 이벤트
ANSI 이스케이프 코드 제어 TTY
Unix 도메인 소켓 또는 명명된 파이프(Windows)를 사용하는 소켓 공유를 통한 IPC
자식 프로세스
스레드 풀
신호 처리
고해상도 시계
스레딩 및 동기화 기본 요소

다이어그램

핸들 및 요청

libuv는 이벤트 루프와 함께 사용할 수 있는 두 가지 추상화, 즉 핸들(handles)과 요청(requests)을 제공합니다.

핸들(handles)은 활성 상태일 때 특정 작업을 수행할 수 있는 장기적인 객체를 나타냅니다.

예시

  • prepare 핸들은 활성 상태일 때마다 루프가 반복될 때 한 번씩 콜백이 호출됩니다.
  • TCP 서버 핸들은 새로운 연결이 있을 때마다 연결 콜백이 호출됩니다.

요청(requests)은 일반적으로 단기적인 작업을 나타냅니다. 이러한 작업은 핸들을 통해 수행될 수 있습니다. 예를 들어, 쓰기 요청은 핸들에 데이터를 쓰는 데 사용됩니다. 또는 독립적으로 수행될 수 있습니다. 예를 들어, getaddrinfo 요청은 핸들이 필요하지 않고 직접 루프에서 실행됩니다.

I/O 루프

libuv의 I/O(또는 이벤트) 루프는 핵심 요소입니다. 모든 I/O 작업을 위한 컨텍스트를 제공하며, 단일 스레드에 묶여서 실행됩니다. 각 이벤트 루프가 다른 스레드에서 실행되는 한 여러 개의 이벤트 루프를 실행할 수 있습니다. libuv 이벤트 루프(혹은 루프나 핸들을 포함하는 기타 API)는 별도로 명시되지 않은 한 스레드 안전(thread-safe)하지 않습니다.

이벤트 루프는 일반적인 단일 스레드 비동기 I/O 방식을 따릅니다. 모든 네트워크 I/O는 비차단(non-blocking) 소켓에서 수행되며, 각 플랫폼에서 가능한 최적의 메커니즘을 사용하여 폴링됩니다. 예를 들어, 리눅스에서는 epoll, OSX 및 기타 BSD 시스템에서는 kqueue, SunOS에서는 event ports, Windows에서는 IOCP가 사용됩니다. 루프 반복(iteration)의 일환으로, 루프는 폴러(poller)에 추가된 소켓의 I/O 활동을 기다리며 대기하게 되며, 소켓 상태(읽기 가능, 쓰기 가능, 연결 끊김 등)를 나타내는 콜백이 실행되어 핸들이 읽기, 쓰기 또는 원하는 I/O 작업을 수행할 수 있게 됩니다.

이벤트 루프가 어떻게 작동하는지 더 잘 이해하기 위해, 루프 반복의 모든 단계를 보여주는 다음 다이어그램이 있습니다.

  1. 루프의 '현재(now)' 개념이 초기화됩니다.

  2. UV_RUN_DEFAULT 모드로 실행된 경우 만료된 타이머가 실행됩니다. 루프의 '현재' 시간 이전으로 예약된 모든 활성 타이머가 콜백을 호출하게 됩니다.

  3. 루프가 활성 상태라면 반복이 시작됩니다. 그렇지 않으면 즉시 종료됩니다. 그렇다면 루프가 언제 활성 상태로 간주될까요? 루프에 활성화 및 참조된 핸들, 활성 요청, 또는 닫히는 핸들이 있는 경우 활성 상태로 간주됩니다.

  4. 대기 중인 콜백이 호출됩니다. 대부분의 경우 모든 I/O 콜백은 I/O 폴링 이후에 호출됩니다. 다만, 특정 경우 이러한 콜백 호출이 다음 루프 반복으로 연기될 수 있습니다. 이전 반복에서 연기된 I/O 콜백이 있다면 이 시점에서 실행됩니다.

  5. idle 핸들 콜백이 호출됩니다. 이름과는 달리, idle 핸들은 활성 상태일 때마다 루프 반복에서 실행됩니다.

  6. prepare 핸들 콜백이 호출됩니다. prepare 핸들은 루프가 I/O로 차단되기 직전에 콜백을 호출합니다.

  7. 폴링 시간 제한(timeout)이 계산됩니다. 루프가 I/O로 차단되기 전에 얼마나 오래 차단할지를 계산합니다. 시간 제한을 계산할 때 다음 규칙이 적용됩니다:

  • UV_RUN_NOWAIT 플래그로 실행된 경우, 시간 제한은 0입니다.
  • 루프가 정지될 예정인 경우(uv_stop()가 호출됨), 시간 제한은 0입니다.
  • 활성 핸들이나 요청이 없는 경우, 시간 제한은 0입니다.
  • 활성 idle 핸들이 있는 경우, 시간 제한은 0입니다.
  • 닫혀야 할 핸들이 있는 경우, 시간 제한은 0입니다.
  • 위의 경우가 모두 해당되지 않으면, 가장 가까운 타이머의 시간 제한을 사용하며, 활성 타이머가 - 없으면 무한대(infinity)가 됩니다.
  • 루프는 I/O로 차단됩니다. 이 시점에서 루프는 이전 단계에서 계산된 기간 동안 I/O로 차단됩니다. 읽기 또는 쓰기 작업을 모니터링하던 모든 I/O 관련 핸들이 이 시점에서 콜백을 호출합니다.
  1. check 핸들 콜백이 호출됩니다. check 핸들은 루프가 I/O로 차단된 후에 콜백을 호출하며, prepare 핸들의 반대 역할을 합니다.

  2. 닫기 콜백이 호출됩니다. 핸들이 uv_close()를 호출하여 닫혔다면 이 시점에서 닫기 콜백이 호출됩니다.

  3. 루프의 '현재(now)' 개념이 업데이트됩니다.

  4. 만료된 타이머가 실행됩니다. 참고로 '현재'가 다시 업데이트되는 것은 다음 루프 반복까지 이루어지지 않습니다. 따라서 다른 타이머를 처리하는 동안 타이머가 만료된 경우 다음 이벤트 루프 반복까지 실행되지 않습니다.

  5. 반복이 종료됩니다. UV_RUN_NOWAIT 또는 UV_RUN_ONCE 모드로 루프가 실행되었다면 반복이 종료되고 uv_run()이 반환됩니다. UV_RUN_DEFAULT 모드로 실행되었다면 여전히 활성 상태라면 처음부터 다시 시작되며, 그렇지 않으면 종료됩니다.

중요
libuv는 비동기 파일 I/O 작업을 가능하게 하기 위해 스레드 풀을 사용하지만, 네트워크 I/O는 항상 각 루프의 스레드에서 단일 스레드로 수행됩니다.

참고
폴링 메커니즘은 다르지만, libuv는 Unix 시스템과 Windows에서 실행 모델을 일관되게 유지합니다.

File I/O

네트워크 I/O와 달리, libuv가 의존할 수 있는 플랫폼별 파일 I/O 원시 기능이 존재하지 않습니다. 따라서 현재 접근 방식은 차단(blocking) 파일 I/O 작업을 스레드 풀에서 실행하는 것입니다.

플랫폼 간 파일 I/O에 대한 자세한 설명은 아래 게시물을 참조하세요(https://blog.libtorrent.org/2012/10/asynchronous-disk-io/).

현재 libuv는 모든 루프가 작업을 큐에 넣을 수 있는 전역 스레드 풀을 사용합니다. 현재 이 풀에서 실행되는 작업 유형은 다음 세 가지입니다.

  • 파일 시스템 작업
  • DNS 함수 (getaddrinfo 및 getnameinfo)
  • uv_queue_work()를 통해 사용자가 지정한 코드

주의
스레드 풀 작업 스케줄링 섹션에서 더 자세한 내용을 확인할 수 있습니다. 그러나 스레드 풀의 크기는 상당히 제한적이라는 점을 염두에 두세요.

0개의 댓글