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


325

저는 노드 프로그래머는 아니지만 단일 스레드 비 차단 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 모델 을 간단한 방법으로 설명 할 수 있습니까? 도와 주시면 감사하겠습니다. :)

답변:


374

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 바운드 워크로드에 잘 작동합니다 .


45
후속 질문 : I / O는 실제로 어떻게 발생합니까? 노드가 시스템에 요청하고 완료되면 알림을 요청합니다. 시스템이 I / O를 수행하는 스레드를 실행 중입니까? 아니면 시스템이 인터럽트를 사용하여 하드웨어 레벨에서 I / O를 비동기식으로 수행합니까? 어딘가에 I / O가 완료 될 때까지 기다려야하며, 완료 될 때까지 차단되고 일정량의 리소스를 소비하게됩니다.
Philip 필립스

6
이 후속 의견은 아래 @ user568109에 의해 답변되었음을 알았습니다.이 두 답변을 병합하는 방법이 있었으면 좋겠습니다.
lfalin

4
나는 당신이 두 배나 긴 응답을 쓸 수 있기를 바랍니다. 그래서 나는 두 배나 더 잘 이해할 것입니다.
Rafael Eyng

노드는 레코드를 위해 많은 곳에서 지원됩니다. MIPS32 라우터 용 펌웨어를 설계 할 때 OpenWRT를 통해 Node.JS를 실행할 수있었습니다.
Qix-MONICA가

아파치보다 점수가 어떻습니까? Apache는 또한 별도의 스레드로 동시 연결을 처리 할 수 ​​있습니다.
Suhail Gupta

210

글쎄, 약간의 관점을 제시하기 위해 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 요청 / 파일 시스템 읽기는 모두 이런 식으로 발생합니다.


16
다이어그램은 매우 유용합니다.
Anmol Saraf

14
다이어그램에 "내부 C ++ 스레드 풀"이 있습니다. 이는 모든 IO 차단 작업이 스레드를 생성한다는 것을 의미합니까? 따라서 내 노드 앱이 모든 요청에 대해 IO가 작동하는 경우 노드 모델과 Apache 모델 사이에 실제로 차이가 있습니까? 이 부분은 죄송합니다.
gav.newalkar

21
@ gav.newalkar 그들은 스레드를 생성하지 않고 요청이 대기합니다. 스레드 풀의 스레드가 처리합니다. 스레드는 동적이 아니며 요청마다 아파치에서와 다릅니다. 일반적으로 고정되어 있으며 시스템마다 다릅니다.
user568109

10
@ user568109 그러나 Apache는 스레드 풀도 사용하고 있습니다 ( httpd.apache.org/docs/2.4/mod/worker.html ). 결국 node.js를 사용한 설정의 차이점은 스레드 풀이있는 곳에서만 Apache를 사용하는 설정과 다릅니다. 그렇지 않습니까?
Kris

13
이 다이어그램은 공식 문서의 첫 페이지에 있어야합니다.
bouvierr 2016 년

52

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

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


1
짧지 만 명확한 답변 (y)
Sudhanshu Gaur

1
좋은 대답 I / O가이 주요 이벤트 루프, 루프 스레드, 요청 스레드 외부에서 발생한다고 덧붙일 것입니다.
Ionut Popa

이것이 제가 2 시간 동안 검색 한 답변입니다. 단일 스레드 응용 프로그램에서 동시성이 어떻게 관리되는지
Muhammad Ramzan

그렇습니다. '다음 단계'답변을 얻기가 어렵습니다. IO 실제로 (다른 곳에서 스레드 풀에서) 완료 얻을 수있는 곳을 설명
올리버 쇼에게

9

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

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

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

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

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

이벤트 루프 상위 뷰

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


5

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

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

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

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

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

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


3

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

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

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


3
"물론 백엔드에는 DB 액세스 및 프로세스 실행을위한 스레드와 프로세스가 있습니다. 그러나 이러한 코드는 명시 적으로 코드에 노출되지 않습니다." -이 문구를 취하면 노드와 노드간에 차이가 없습니다. 또는 다중 스레드 프레임 워크 또는 Java의 Spring 프레임 워크라고 가정 해 봅시다. 스레드가 있지만 생성을 제어하지는 않습니다.
Rafael Eyng

@RafaelEyng 일련의 여러 요청을 처리하기 위해 노드는 항상 단일 스레드를 갖습니다. 각 콜백이 db 액세스와 같은 다른 프로세스를 제외하고 새 스레드 인스턴스에 배치되는지 확실하지 않지만 적어도 처리하기 전에 줄을 기다려야하는 요청을받을 때마다 노드가 스레드를 인스턴스화하지 않는다는 것을 알고 있습니다 (실행 전 콜백).
콜드 서 베루스

1

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

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

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

명확한? :-)

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


kqueue epoll은 Linux 커널에서 확장 가능한 비동기 I / O 알림을위한 것입니다. 노드에는 libuv가 있습니다. 노드는 전적으로 사용자 영역에 있습니다. 커널이 구현하는 것에 의존하지 않습니다.
user568109

1
@ user568109에서 libuv는 Node의 중개인입니다. 모든 비동기 프레임 워크는 커널에서 일부 비동기 I / O 지원에 의존합니다 (직접적으로 또는 아닙니다). 그래서?
Robert Siemer

혼란을 드려 죄송합니다. 소켓 작업에는 커널의 비 차단 I / O가 필요합니다. 비동기 처리를 처리합니다. 그러나 비동기 파일 I / O는 libuv 자체에 의해 처리됩니다. 당신의 대답은 그것을 말하지 않습니다. 커널에 의해 처리되는 것과 동일하게 취급합니다.
user568109
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.