이 글은 Nodejs의 비동기 I/O에 대한 사전 이해를 제공합니다. 도움이 필요한 친구들이 모두 참고할 수 있기를 바랍니다.
"비동기"라는 용어는 실제로 Node.js 이전에 탄생했습니다. 그러나 대부분의 고급 프로그래밍 언어에서는 비동기성이 거의 발생하지 않습니다. 많은 고급 언어나 운영 플랫폼 중에서 Node는 비동기식을 주요 프로그래밍 방식과 설계 개념으로 사용한 최초의 언어입니다. [관련 추천: "nodejs Tutorial"]
비동기 I/O, 이벤트 중심 및 단일 스레드가 Node의 기조를 구성하며 Nginx와 Node의 이벤트 중심 및 비동기 I/O 설계 개념은 비교적 유사합니다. . Nginx는 순수 C로 작성되었으며 성능이 뛰어나고 클라이언트 연결을 관리하는 강력한 기능을 갖추고 있지만 여전히 다양한 동기화 프로그래밍 언어로 인해 제한됩니다. 그러나 Node는 클라이언트가 가져온 많은 수의 동시 요청을 처리하는 서버로 사용될 수 있으며, 네트워크의 다양한 애플리케이션에 동시 요청을 보내는 클라이언트로도 사용될 수 있습니다.
Node에서 비동기 I/O가 중요한 이유는 Node가 네트워크용으로 설계되었기 때문입니다. 교차 네트워크 구조에서 동시성은 현대 프로그래밍의 표준 장비가 되었습니다.
스크립트 실행 시간이 100밀리초를 초과하면 사용자는 페이지가 멈춘 듯한 느낌을 받고 페이지가 응답을 멈췄다고 생각할 것이라고 "고성능 JavaScript"에 언급되어 있습니다. B/S 모델에서는 네트워크 속도 제한으로 인해 웹 페이지의 실시간 경험에 큰 문제가 발생합니다.
웹 페이지가 일시적으로 리소스를 가져와 동기식으로 가져와야 하는 경우 JavaScript는 실행을 계속하기 전에 서버에서 리소스를 완전히 가져올 때까지 기다려야 합니다. 이 기간 동안 UI는 일시 중지되고 응답하지 않습니다. 사용자의 상호작용. 이 사용자 경험은 매우 좋지 않습니다. 비동기식 요청을 사용하면 리소스를 다운로드하는 동안 JavaScript 및 UI 실행이 대기 상태가 아니며 계속해서 사용자 상호 작용에 응답할 수 있습니다.
마찬가지로 프런트엔드는 비동기 작업을 통해 UI 차단을 제거할 수 있지만 프런트엔드가 리소스를 얻는 속도도 백엔드의 응답 속도에 따라 달라집니다. 리소스가 서로 다른 두 위치의 데이터 반환에서 나온 경우 첫 번째 리소스는 M밀리초를 소비하고 두 번째 리소스는 N밀리초를 소비합니다. 동기화 방법을 사용하는 경우 두 리소스를 얻는 데 소요되는 시간은 M+N 밀리초입니다. 비동기 방식에서는 첫 번째 자원 획득이 두 번째 자원 획득을 막지 않으며, 소요되는 시간은 max(M,N)이다.
웹사이트나 애플리케이션이 계속해서 확장됨에 따라 M과 N의 값은 선형적으로 증가할 것이며, 그러면 비동기 성능이 동기 성능보다 더 우수해질 것입니다.
비즈니스 시나리오에서 완료해야 하는 일련의 관련 없는 작업이 있다고 가정합니다. 두 가지 주요 방법이 있습니다:
생성하는 경우 멀티스레딩은 병렬 실행보다 오버헤드가 적으므로 멀티스레딩을 선호합니다. 그러나 멀티스레딩은 스레드 생성 및 실행 중 스레드 컨텍스트 전환, 멀티스레드 프로그래밍에 큰 오버헤드가 있습니다. 종종 잠금 및 상태 동기화와 같은 문제에 직면합니다.
단일 스레드 작업 순차 실행의 단점은 성능입니다. 약간 느린 작업으로 인해 후속 코드 실행이 차단됩니다. 컴퓨터 리소스에서 I/O 및 CPU 계산은 일반적으로 병렬로 실행될 수 있지만 동기식 프로그래밍 모델에서는 I/O가 후속 작업을 기다리게 되므로 리소스가 더 잘 활용되지 않습니다.
노드는 단일 스레드를 사용하여 다중 스레드 교착 상태, 상태 동기화 및 기타 문제를 방지합니다. 비동기 I/O를 사용하여 단일 스레드가 차단되지 않도록 하고 CPU를 더 잘 활용합니다.
비동기 I/O는 Node에서 가장 널리 사용되지만 Node에 독창적인 것은 아닙니다.
컴퓨터 커널 I/O의 경우 비동기/동기 및 차단/비차단은 서로 다른 두 가지입니다.
운영 체제에는 차단 및 비차단이라는 두 가지 I/O 방법만 있습니다. 차단 I/O를 호출할 때 애플리케이션은 결과를 반환하기 전에 I/O가 완료될 때까지 기다려야 합니다.
블로킹 I/O의 특징 중 하나는 호출 후 호출이 끝나기 전에 시스템 커널 수준에서 모든 작업이 완료될 때까지 기다려야 한다는 것입니다. I/O를 차단하면 CPU가 I/O를 기다리게 되어 대기 시간이 낭비되고 CPU의 처리 능력을 최대한 활용하지 못하게 됩니다.
성능 향상을 위해 커널은 비차단 I/O를 제공합니다. Non-Blocking I/O와 Blocking I/O의 차이점은 호출 직후에 반환된다는 점입니다. Non-Blocking I/O가 반환된 후에는 CPU 타임 슬라이스를 사용하여 다른 작업을 처리할 수 있습니다. 성능 향상은 당연하지만 I/O 완료로 인해 I/O가 완료되지 않고 즉시 반환되는 것은 비즈니스 계층에서 기대하는 데이터가 아니라 현재 호출 상태뿐입니다.
완전한 데이터를 얻으려면 애플리케이션은 I/O 작업을 반복적으로 호출하여 완료를 확인해야 합니다. 작업이 완료되었는지 확인하기 위해 반복적으로 호출하는 기술을 폴링이라고 합니다.
기존 폴링 기술에는 주로 읽기, 선택, 폴링, epoll 및 kqueue가 포함됩니다. 여기서는 epoll의 폴링 원리에 대해서만 이야기하겠습니다.
epoll은 Linux에서 가장 효율적인 I/O 이벤트 알림 메커니즘입니다. 폴링에 들어갈 때 I/O 이벤트가 감지되지 않으면 이벤트가 발생할 때까지 절전 모드로 전환됩니다. 쿼리를 순회하는 대신 실제로 이벤트 알림 및 실행 콜백 방법을 사용하므로 CPU를 낭비하지 않고 실행 효율성이 더 높습니다.
폴링 기술은 완전한 데이터 획득을 보장하기 위한 비차단 I/O의 요구 사항을 충족하지만 프로그램의 경우 애플리케이션이 여전히 I/O를 기다려야 하기 때문에 일종의 동기화입니다. 완전히 반환하려면 여전히 많은 시간을 기다려야 합니다. 기다리는 동안 CPU는 파일 설명자를 반복하는 데 사용되거나 시간이 발생할 때까지 기다리는 동안 절전 모드로 사용됩니다.
일부 스레드가 차단 I/O 또는 비차단 I/O와 폴링 기술을 수행하도록 하여 하나의 스레드가 계산 처리를 수행하고 스레드를 통해 통신할 수 있도록 하여 데이터 수집을 완료합니다. (시뮬레이션이긴 하지만)
그러나 처음에는 Node가 libeio와 libev를 사용하여 *nix 플랫폼에서 I/O 부분을 구현하여 비동기 I/O를 구현했습니다. 영형. Node v0.9.3에서는 비동기 I/O를 완료하기 위해 스레드 풀이 구현되었습니다.
Windows의 IOCP는 Li Xiang의 비동기 I/O를 어느 정도 제공합니다. 즉, 비동기 메서드 호출, I/O 완료 후 알림 대기, 콜백 실행 등이 있습니다. 사용자는 폴링을 고려할 필요가 없습니다. 그러나 내부는 여전히 스레드 풀 원칙을 기반으로 합니다. 차이점은 이러한 스레드 풀이 시스템 커널에 의해 관리된다는 것입니다.
Windows 플랫폼과 *nix 플랫폼의 차이로 인해 Node는 libuv를 추상 캡슐화 계층으로 제공하므로 모든 플랫폼 호환성 판단은 이 계층에서 완료되며 상위 계층 Node와 하위 계층 사용자 정의 스레드가 pool 및 IOCP 문자는 독립적입니다.
Node가 단일 스레드라고 자주 언급합니다. 여기서 단일 스레드는 JavaScript가 단일 스레드에서 실행된다는 의미입니다. Node에는 *nix든 Windows 플랫폼이든 내부적으로 I/O 작업을 완료하는 또 다른 스레드 풀이 있습니다.
는 이벤트 루프, 관찰자, 요청 개체 등으로 전체 비동기 I/O 링크를 완성합니다.
이벤트 루프는 콜백 함수를 매우 흔하게 만드는 Node의 자체 실행 모델입니다.
프로세스가 시작되면 Node는 while(true)와 유사한 루프를 생성합니다. 루프 본문이 실행될 때마다 이를 Tick이라고 합니다. 각 Tick의 과정은 처리할 이벤트가 있는지 확인하고, 있으면 해당 이벤트와 관련 콜백 함수를 검색하는 것입니다. 연관된 콜백 함수가 있으면 실행하십시오. 그런 다음 다음 루프에 들어가고 더 이상 처리할 이벤트가 없으면 프로세스를 종료합니다.
각 이벤트 루프에는 하나 이상의 관찰자가 있으며, 처리할 이벤트가 있는지 확인하는 프로세스는 이러한 관찰자에게 처리할 이벤트가 있는지 묻는 것입니다.
Node에서 이벤트는 주로 네트워크 요청, 파일 I/O 등에서 발생합니다. 이러한 시간에 해당하는 관찰자에는 파일 I/O 관찰자, 네트워크 I/O 관찰자 등이 포함됩니다. 관찰자는 이벤트를 분류했습니다.
이벤트 루프는 일반적인 생산자/소비자 모델입니다. 비동기 I/O, 네트워크 요청 등은 이벤트를 생성하며 노드에 지속적으로 다양한 유형의 이벤트를 제공합니다. 이러한 이벤트는 해당 관찰자에게 전달되고 이벤트 루프는 관찰자로부터 이벤트를 꺼내 처리합니다.
Node의 비동기 I/O 호출의 경우 개발자가 콜백 함수를 호출하지 않습니다. JavaScript가 호출을 시작할 때부터 커널이 I/O 작업을 완료할 때까지의 전환 과정에서 request object
라는 제품이 있습니다. fs.open() 메서드는 아래의 작은 예로 사용됩니다.
fs.open = function(path,flags,mode,callback){ //... binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback); }
fs.open()은 지정된 경로와 매개변수를 기반으로 파일을 열어 파일 설명자를 얻는 데 사용됩니다. 이는 모든 후속 I/O 작업에 대한 예비 작업입니다. JavaScript 수준 코드는 C++ 핵심 모듈을 호출하여 하위 수준 작업을 수행합니다.
JavaScript는 Node의 핵심 모듈을 호출합니다. 핵심 모듈은 C++ 모듈을 호출하고, 내장 모듈은 libuv를 통해 시스템 호출을 수행합니다. 여기서 libuv는 캡슐화 계층 역할을 하며 기본적으로 uv_fs_open() 메서드를 호출하는 두 가지 플랫폼 구현을 갖습니다. uv_fs_open() 호출 프로세스 중에 JavaScript 레이어에서 전달된 매개 변수와 현재 메서드가 요청 개체에 캡슐화되고 콜백 함수가 이 개체의 속성에 설정됩니다. 개체가 패키징된 후 개체는 스레드 풀에 푸시되어 실행을 기다립니다.
이 시점에서 JavaScript 호출은 즉시 반환되고 JavaScript 수준에서 시작된 비동기 호출의 첫 번째 단계가 종료됩니다. JavaScript 스레드는 현재 작업에 대한 후속 작업을 계속 수행할 수 있습니다.
요청 개체는 비동기 I/O 프로세스의 중요한 중간 제품이며 I/O 작업이 완료된 후 실행 및 콜백 처리를 대기하기 위해 스레드 풀로 전송되는 것을 포함하여 모든 상태가 이 개체에 저장됩니다.
요청 개체를 어셈블하여 실행을 위해 I/O 스레드 풀로 보내는 것은 I/O 완료의 첫 번째 부분일 뿐이며 콜백 알림은 두 번째 부분입니다.
스레드 풀의 I/O 작업이 호출된 후 얻은 결과는 req->result 속성에 저장되고 PostQueueCompletionStatus()가 호출되어 현재 개체 작업이 완료되었음을 IOCP에 알립니다.
이 시점에서 전체 비동기 I/O 프로세스가 완전히 끝났습니다.
이벤트 루프, 관찰자, 요청 개체 및 I/O 스레드 풀이 함께 노드 비동기 I/O 모델의 기본 요소를 구성합니다.
정리하면 비동기 I/O의 여러 키워드인 단일 스레드, 이벤트 루프, 관찰자 및 I/O 스레드 풀을 추출할 수 있습니다. 단일 스레드와 스레드 풀은 약간 역설적인 것 같습니다. JavaScript는 단일 스레드이기 때문에 멀티 코어 CPU를 최대한 활용할 수 없다는 것을 이해하기 쉽습니다. 실제로 Node에서는 JavaScript가 단일 스레드라는 점을 제외하면 Node 자체는 실제로 다중 스레드이지만 I/O 스레드는 CPU를 덜 사용합니다. 또한, 사용자 코드를 병렬로 실행할 수 없는 점을 제외하면 모든 I/O(디스크 I/O, 네트워크 I/O 등)를 병렬로 실행할 수 있습니다.
더 많은 프로그래밍 관련 지식을 보려면 프로그래밍 비디오를 방문하세요! !
위 내용은 Nodejs의 비동기 I/O에 대한 사전 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!