[node.js] Node.js에서 단일 스레드 비 차단 IO 모델 작동 방식

저는 노드 프로그래머는 아니지만 단일 스레드 비 차단 IO 모델의 작동 방식에 관심이 있습니다. understand-the-node-js-event-loop 기사를 읽은 후에는 정말 혼란 스럽습니다. 모델에 대한 예를 제공했습니다.

c.query(
   'SELECT SLEEP(20);',
   function (err, results, fields) {
     if (err) {
       throw err;
     }
     res.writeHead(200, {'Content-Type': 'text/html'});
     res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');
     c.end();
    }
);

Que : 단일 스레드 만 있기 때문에 요청 A (먼저 오는)와 B가 두 개인 경우, 서버 측 프로그램은 먼저 요청 A를 처리합니다. 그리고 프로그램이 I/O대기 중에 멈춰 웹 페이지를 렌더링하는 코드를 실행할 수 없습니다. 대기하는 동안 프로그램이 요청 B로 전환됩니까? 내 의견으로는 단일 스레드 모델로 인해 하나의 요청을 다른 요청으로 전환 할 수있는 방법이 없습니다. 그러나 예제 코드의 제목은 코드를 제외한 모든 것이 병렬로 실행 된다고 말합니다 .

(PS 노드를 사용한 적이 없기 때문에 코드를 잘못 이해했는지 확실하지 않습니다.) 대기 중에 노드를 A로 B로 전환하는 방법은 무엇입니까? 그리고 노드 의 단일 스레드 비 차단 IO 모델 을 간단한 방법으로 설명 할 수 있습니까? 도와 주시면 감사하겠습니다. 🙂



답변

Node.js는 지원되는 OS (최소한 Unix, OS X 및 Windows)가 제공하는 비동기 (비 차단) 입력 / 출력에 대한 apis / syscall을 추상화하는 크로스 플랫폼 라이브러리 인 libuv 기반으로 구축되었습니다 .

비동기식 IO

이 프로그래밍 모델에서 파일 시스템에 의해 관리되는 장치 및 리소스 (소켓, 파일 시스템 등)에 대한 열기 / 읽기 / 쓰기 작업 은 호출 스레드를 차단하지 않으며 (일반적인 동기식 c- 유사 모델에서와 같이) 새 데이터 또는 이벤트가 사용 가능할 때 알림을받을 프로세스 (커널 / OS 레벨 데이터 구조). 웹 서버와 같은 앱의 경우, 프로세스는 통지 된 이벤트가 속하는 요청 / 컨텍스트를 파악하고 거기서 요청을 처리해야합니다. 이것은 단일 스레드 프로세스가 새로운 이벤트를 처리하기 위해 프로세스의 디스패처에 요청해야했기 때문에 요청을 OS로 보낸 스택 프레임과는 다른 스택 프레임에 있다는 것을 의미합니다.

내가 설명 한 모델의 문제점은 프로그래머가 본질적으로 비 순차적이기 때문에 추론하기가 익숙하지 않으며 어렵다는 것입니다. “함수 A에서 요청을하고 결과를 A의 지역 주민이 일반적으로 사용할 수없는 다른 기능에서 처리해야합니다.”

노드의 모델 (계속 진행 스타일 및 이벤트 루프)

Node는 프로그래머가 특정 프로그래밍 스타일을 사용하도록 유도함으로써이 모델을 좀 더 동 기적으로 보이도록 Javascript의 언어 기능을 활용하는 문제를 해결합니다. IO를 요청하는 모든 함수에는 서명 function (... parameters ..., callback)이 있고 요청 된 작업이 완료 될 때 호출 될 콜백이 제공되어야합니다 (대부분의 시간은 OS가 완료를 알리는 데 대기하는 데 소요됩니다. 다른 일을하면서 보냈다). Javascript의 클로저 지원을 통해 콜백 본문 내부의 외부 (호출) 함수에 정의한 변수를 사용할 수 있습니다. 이렇게하면 노드 런타임에서 독립적으로 호출하는 다른 함수간에 상태를 유지할 수 있습니다. 연속 통과 스타일 도 참조하십시오 .

또한 IO 연산을 생성하는 함수를 호출 한 후 호출 함수는 일반적 return으로 노드의 이벤트 루프를 제어합니다 . 이 루프는 실행 예약 된 다음 콜백 또는 함수를 호출합니다 (대개 해당 이벤트가 OS에 의해 통지 되었기 때문에)-여러 요청을 동시에 처리 할 수 ​​있습니다.

노드의 이벤트 루프 는 커널의 디스패처와 다소 비슷 하다고 생각할 수 있습니다 . 보류중인 IO가 완료되면 커널은 차단 된 스레드의 실행을 예약하고 해당 이벤트가 발생하면 노드는 콜백을 예약합니다.

동시성이 높고 병렬 처리 없음

마지막으로 “코드를 제외한 모든 것이 병렬로 실행된다”는 문구는 노드가 코드를 단일 스레드로 수십만 개의 열린 소켓의 요청을 동시에 처리하여 모든 j를 멀티플렉싱하고 시퀀싱함으로써 점을 포착하는 적절한 작업을 수행합니다. 단일 실행 스트림의 논리 ( “모든 것이 병렬로 실행된다”고 말하더라도 여기서는 정확하지 않을 수 있습니다. 동시성 대 병렬성 -차이점은 무엇입니까? 참조 ) 이것은 대부분의 시간이 실제로 네트워크 또는 디스크 (데이터베이스 / 소켓)를 기다리는 데 소비되고 논리는 실제로 CPU를 많이 사용하지 않으므로 웹 응용 프로그램 서버에서 잘 작동합니다 . 즉, 이것은 IO 바운드 워크로드에 잘 작동합니다 .


답변

글쎄, 약간의 관점을 제시하기 위해 node.js와 Apache를 비교해 보겠습니다.

Apache는 다중 스레드 HTTP 서버이며 서버가 수신하는 모든 요청마다 해당 요청을 처리하는 별도의 스레드를 만듭니다.

반면 Node.js는 이벤트 중심으로 단일 스레드에서 모든 요청을 비동기 적으로 처리합니다.

Apache에서 A와 B가 수신되면 요청을 처리하는 두 개의 스레드가 작성됩니다. 각각은 쿼리를 개별적으로 처리하고 페이지를 제공하기 전에 쿼리 결과를 기다립니다. 이 페이지는 쿼리가 완료 될 때까지만 제공됩니다. 서버가 결과를 수신 할 때까지 나머지 스레드를 실행할 수 없으므로 쿼리 페치가 차단됩니다.

노드에서 c.query는 비동기 적으로 처리됩니다. 즉, c.query는 A에 대한 결과를 가져 오는 동안 B에 대한 c.query를 처리하기 위해 점프하며 A에 대한 결과가 도착하면 콜백으로 결과를 다시 보냅니다. 응답. 페치가 완료되면 Node.js는 콜백을 실행하는 것을 알고 있습니다.

제 생각에는 단일 스레드 모델이기 때문에 한 요청에서 다른 요청으로 전환 할 수있는 방법이 없습니다.

실제로 노드 서버는 항상 당신을 위해 정확하게 그것을 수행합니다. 스위치 (비동기 동작)를 만들려면 사용하는 대부분의 함수에 콜백이 있습니다.

편집하다

SQL 쿼리는 mysql 라이브러리 에서 가져옵니다 . 콜백 스타일과 이벤트 이미 터를 구현하여 SQL 요청을 대기열에 넣습니다. 비 차단 I / O의 추상화를 제공하는 내부 libuv 스레드에 의해 비동기 적으로 실행되지 않습니다 . 쿼리를 만들기 위해 다음 단계가 수행됩니다.

  1. db에 대한 연결을 열고 연결 자체를 비동기 적으로 만들 수 있습니다.
  2. db가 연결되면 쿼리가 서버로 전달됩니다. 쿼리를 큐에 넣을 수 있습니다.
  3. 메인 이벤트 루프는 콜백 또는 이벤트로 완료를 통보받습니다.
  4. 메인 루프는 콜백 / 이벤트 핸들러를 실행합니다.

http 서버로 들어오는 요청은 비슷한 방식으로 처리됩니다. 내부 스레드 아키텍처는 다음과 같습니다.

node.js 이벤트 루프

C ++ 스레드는 비동기 I / O (디스크 또는 네트워크)를 수행하는 libuv 스레드입니다. 스레드 풀에 요청을 디스패치 한 후에 기본 이벤트 루프가 계속 실행됩니다. 대기 또는 절전 모드가 아니므로 더 많은 요청을 수락 할 수 있습니다. SQL 쿼리 / HTTP 요청 / 파일 시스템 읽기는 모두 이런 식으로 발생합니다.


답변

Node.js는 무대 뒤에서 libuv를 사용 합니다. libuv 에는 스레드 풀이 있습니다 (기본적으로 크기 4). 따라서 Node.js 는 스레드사용 하여 동시성을 달성합니다.

그러나 , 코드의 단일 스레드에서 실행 (즉, Node.js를 함수의 콜백 모두 동일한 스레드에서 호출됩니다, 소위 루프 스레드 또는 이벤트 루프). 사람들이 “Node.js는 단일 스레드에서 실행된다”고 말할 때 실제로 “Node.js의 콜백은 단일 스레드에서 실행됩니다”라고 말합니다.


답변

Node.js는 이벤트 루프 프로그래밍 모델을 기반으로합니다. 이벤트 루프는 단일 스레드에서 실행되며 반복적으로 이벤트를 기다린 다음 해당 이벤트에 등록 된 이벤트 핸들러를 실행합니다. 예를 들어 이벤트는

  • 타이머 대기가 완료되었습니다
  • 다음 데이터 청크가이 파일에 기록 될 준비가되었습니다.
  • 새로운 HTTP 요청이 나옵니다

이 모든 것은 단일 스레드에서 실행되며 JavaScript 코드는 병렬로 실행되지 않습니다. 이러한 이벤트 핸들러가 작고 더 많은 이벤트 자체를 기다리는 한 모든 것이 잘 작동합니다. 이를 통해 단일 Node.js 프로세스에서 여러 요청을 동시에 처리 할 수 ​​있습니다.

(이벤트가 시작되는 위치와 관련하여 약간의 마술이 있습니다. 일부는 병렬로 실행되는 저수준 작업자 스레드와 관련이 있습니다.)

이 SQL의 경우 데이터베이스 쿼리 작성과 콜백에서 결과 가져 오기 사이에 많은 일 (이벤트)이 발생합니다 . 이 시간 동안 이벤트 루프는 응용 프로그램으로 수명을 펌핑하고 한 번에 하나의 작은 이벤트로 다른 요청을 진행시킵니다. 따라서 여러 요청이 동시에 제공됩니다.

이벤트 루프 상위 뷰

“Node.js 뒤의 10,000ft 핵심 개념의 이벤트 루프” 에 따르면 .


답변

c.query () 함수에는 두 개의 인수가 있습니다

c.query("Fetch Data", "Post-Processing of Data")

이 경우 “데이터 가져 오기”작업은 DB-Query입니다. 이제 작업자 스레드를 생성하고 DB-Query를 수행하는이 작업을 제공하여 Node.js에서 처리 할 수 ​​있습니다. (Node.js는 내부적으로 스레드를 만들 수 있음을 기억하십시오). 이를 통해 지연없이 기능을 즉시 반환 할 수 있습니다.

“데이터 후 처리”의 두 번째 인수는 콜백 함수이며, 노드 프레임 워크는이 콜백을 등록하고 이벤트 루프에 의해 호출됩니다.

따라서 명령문 c.query (paramenter1, parameter2)은 즉시 리턴되어 노드가 다른 요청을 수용 할 수있게합니다.

추신 : 방금 노드를 이해하기 시작했습니다. 실제로 이것을 @Philip에 대한 의견으로 작성 하고 싶었지만 평판이 충분하지 않아 답변으로 작성했습니다.


답변

“물론 백엔드에는 DB 액세스 및 프로세스 실행을위한 스레드와 프로세스가 있습니다. 그러나 이러한 코드는 코드에 명시 적으로 노출되지 않으므로 알지 못하는 것 외에는 걱정할 필요가 없습니다. “예를 들어, 데이터베이스 또는 다른 프로세스와의 I / O 상호 작용은 각 스레드의 결과가 이벤트 루프를 통해 코드로 반환되므로 각 요청의 관점에서 비동기 적입니다.”

about- “코드를 제외한 모든 것이 병렬로 실행”-IO 대기와 같은 비동기 작업을 호출 할 때마다 이벤트 루프가 모든 것을 처리하고 콜백을 호출합니다. 당신이 생각해야 할 것이 아닙니다.

귀하의 예 : 두 가지 요청 A (첫 번째로 제공됨)와 B가 있습니다. 요청 A를 실행하면 코드는 계속 동 기적으로 실행되고 요청 B를 실행합니다. 이벤트 루프는 요청 A를 처리합니다. 결과는 요청 B와 동일합니다.


답변

자, 지금까지 대부분의 것들이 명확해야합니다 … 까다로운 부분은 SQL입니다 . 실제로 다른 스레드 또는 프로세스 에서 실행되고 있지 않으면 SQL 실행 을 개별 단계로 세분 해야합니다 . SQL 프로세서는 비 블로킹 사람이 실행되는 곳)! 비동기 실행을 위해 만든 및 차단 것들 (예를 들면 슬립) 실제로 수있는 알람 인터럽트 / 이벤트로 (커널로 전송)과의 이벤트 목록에 넣을 수 메인 루프.

즉, 예를 들어 SQL 등의 해석은 즉시 수행되지만 대기 중에 (일부 커널에서 kqueue, epoll, … 구조로 다른 이벤트와 함께 저장 됨) 다른 IO 작업과 함께 ) 메인 루프는 다른 일을 할 수 있으며 결국 IO에 어떤 일이 발생했는지 기다리고 기다릴 수 있습니다.

다시 말하면, 프로그램은 결코 멈추지 않으며, 수면 호출은 실행되지 않습니다. 그들의 임무는 커널 (무언가 쓰기, 무언가가 네트워크를 통해 오기를 기다림, 시간이 경과하기를 기다림) 또는 다른 스레드 또는 프로세스에 의해 수행됩니다. – 노드 프로세스는 각 이벤트 루프주기마다 한 번 OS에 대한 호출을 차단할 때 커널이 해당 작업 중 하나 이상을 완료했는지 확인합니다. 모든 비 차단이 완료되면 그 시점에 도달합니다.

명확한? 🙂

나는 노드를 모른다. 그러나 c.query는 어디에서 왔습니까?