이 기사는 실제 개발 및 학습에서 nodejs에 대한 개인적인 이해입니다. 나중에 참고할 수 있도록 작성되었습니다.
I/O : 입력/출력(Input/Output), 시스템의 입력 및 출력.
시스템은 사람과 같은 개인으로 이해될 수 있습니다. 말을 하면 출력이 되고, 들을 때는 입력이 됩니다.
Blocking I/O와 Non-Blocking I/O의 차이점은 입력에서 출력까지의 기간 동안 시스템이 다른 입력을 받을 수 있는지 여부에 있습니다.
다음은 차단 I/O와 비차단 I/O가 무엇인지 보여주는 두 가지 예입니다.
1. 쿠킹
먼저 시스템의 범위를 결정해야 합니다. 이 예에서는 식당의 이모와 웨이터가 시스템으로 간주되어 주문이 출력됩니다 .
그러면 음식을 주문하고 서빙하는 사이에 다른 사람의 주문을 받아들일 수 있는지, Blocking I/O인지 Non-Blocking I/O인지 판단할 수 있습니다.
구내식당 이모님은 주문 시 다른 학생들에게 주문을 할 수 없으며, 학생이 주문을 마치고 음식을 대접한 후에야 다음 학생의 주문을 받아들일 수 있기 때문에 구내식당 이모님이 I/O를 막고 있습니다.
레스토랑 웨이터의 경우 주문 후, 손님이 요리를 제공하기 전에 다음 손님에게 서비스를 제공할 수 있으므로 웨이터는 비차단 I/O를 갖습니다.
2. 집안일을 해라
옷을 세탁할 때 세탁기를 기다릴 필요 없이 이때 바닥을 쓸고 책상을 정리하면 됩니다. 총 25분.
세탁은 실제로 옷을 세탁기에 넣는 것과 세탁을 마치는 사이에 다른 작업을 수행할 수 있는 비차단 I/O입니다.
Non-Blocking I/O가 성능을 향상시킬 수 있는 이유는 불필요한 대기 시간을 줄일 수 있기 때문입니다.
비차단 I/O를 이해하는 핵심은
Nodejs의 Non-Blocking I/O는 어떻게 반영되나요? 앞서 언급했듯이 Non-Blocking I/O를 이해하는 데 있어서 중요한 점은 먼저 시스템 경계를 결정하는 것입니다. 노드의 시스템 경계는 메인 스레드 입니다.
아래 아키텍처 다이어그램을 스레드 유지 관리에 따라 구분하면 왼쪽 점선은 nodejs 스레드이고 오른쪽 점선은 C++ 스레드입니다.
이제 nodejs 스레드는 데이터베이스를 쿼리해야 합니다. 이는 일반적인 I/O 작업이며, I/O 결과를 기다리지 않고 계속해서 다른 작업을 처리합니다. 계산용 스레드.
결과가 나올 때까지 기다렸다가 nodejs 스레드로 반환합니다. 결과를 얻기 전에 nodejs 스레드는 다른 I/O 작업도 수행할 수 있으므로 비차단입니다.
nodejs 스레드는 왼쪽 부분이 웨이터이고 C++ 스레드는 요리사와 동일합니다.
따라서 노드의 Non-Blocking I/O는 C++ 작업자 스레드를 호출하여 완료됩니다.
그렇다면 C++ 스레드가 결과를 얻을 때 nodejs 스레드에 어떻게 알릴까요? 대답은 이벤트 중심 입니다.
차단 : 프로세스는 I/O 중에 휴면 상태를 유지하고 다음 단계로 진행하기 전에 I/O가 완료될 때까지 기다립니다.
비차단 : I/O 중에 함수가 즉시 반환되고 프로세스는 I/O를 기다리지 않습니다. 아 완료하세요.
따라서 반환된 결과를 확인하려면 이벤트 드라이버를 사용해야 합니다.
소위 이벤트 기반은 프런트 엔드 클릭 이벤트와 동일하게 이해될 수 있습니다. 먼저 클릭 이벤트를 작성하지만 언제 트리거될지 알 수 없습니다. 이벤트 기반 기능을 실행합니다.
이 모드는 관찰자 모드이기도 합니다. 즉, 먼저 이벤트를 수신한 다음 트리거되면 실행합니다.
그렇다면 이벤트 드라이브를 구현하는 방법은 무엇입니까? 대답은 비동기 프로그래밍 입니다.
위에서 언급한 것처럼 nodejs에는 non-blocking I/O가 많기 때문에 콜백 함수를 통해 non-blocking I/O의 결과를 얻어야 하는 것이 바로 비동기 프로그래밍입니다 . 예를 들어, 다음 코드는 콜백 함수를 통해 결과를 얻습니다.
glob(__dirname+'/**/*', (err, res) => { 결과 = 해상도 console.log('결과 가져오기') })
nodejs 콜백 함수의 첫 번째 매개변수는 error 이고 그 다음 매개변수는 결과 입니다 . 왜 이런 일을 하는가?
노력하다 { 인터뷰(함수 () { console.log('미소') }) } 잡기(err) { console.log('울음', 오류) } 함수 인터뷰(콜백) { setTimeout(() => { if(Math.random() < 0.1) { 콜백('성공') } 또 다른 { 새로운 오류 발생('실패') } }, 500) }
실행 후에는 잡히지 않았고 오류가 전역적으로 발생하여 전체 nodejs 프로그램이 중단되었습니다.
setTimeout이 이벤트 루프를 다시 열므로 try catch에 의해 포착되지 않습니다. 이벤트 루프가 열릴 때마다 호출 스택 컨텍스트가 다시 생성됩니다. setTimeout 콜백 함수가 실행될 때 Try catch는 이전 이벤트 루프의 호출 스택에 속합니다. 호출 스택 컨텍스트 모든 것이 다릅니다. 이 새 호출 스택에는 try catch가 없으므로 오류가 전역적으로 발생하고 포착될 수 없습니다. 자세한 내용은 비동기 대기열에서 try catch를 수행할 때 발생하는 문제를 참조하세요.
그럼 무엇을 해야 할까요? 오류를 매개변수로 전달합니다.
function 인터뷰(콜백) { setTimeout(() => { if(Math.random() < 0.5) { 콜백('성공') } 또 다른 { 콜백(새 오류('실패')) } }, 500) } 인터뷰(함수(res) { if (res 인스턴스오류) { console.log('울음') 반품 } console.log('미소') })
하지만 이는 더 번거롭고 콜백에서 판단해야 하므로 성숙한 규칙이 있습니다. 첫 번째 매개변수가 존재하지 않으면 실행이 성공한 것입니다.
함수 인터뷰(콜백) { setTimeout(() => { if(Math.random() < 0.5) { 콜백(null, '성공') } 또 다른 { 콜백(새 오류('실패')) } }, 500) } 인터뷰(함수(res) { 만약 (res) { 반품 } console.log('미소') })
nodejs의 콜백 작성 방식은 콜백 영역을 발생시킬 뿐만 아니라 비동기 프로세스 제어 문제도 발생시킵니다.
비동기 프로세스 제어는 주로 동시성이 발생할 때 동시성 논리를 처리하는 방법을 나타냅니다. 위의 예를 사용하면 동료가 두 회사를 인터뷰하는 경우 두 회사를 성공적으로 인터뷰할 때까지 세 번째 회사의 인터뷰를 받지 않습니다. 그렇다면 이 논리를 어떻게 작성해야 할까요? 전역적으로 변수 개수를 추가해야 합니다.
var count = 0 인터뷰((err) => { 만약 (오류) { 반품 } 카운트++ if (개수 >= 2) { // 처리 로직} }) 인터뷰((err) => { 만약 (오류) { 반품 } 카운트++ if (개수 >= 2) { // 처리 로직} })
위와 같이 작성하는 것은 매우 번거롭고 보기 흉합니다. 따라서 promise와 async/await의 작성 방법은 나중에 등장했습니다.
현재 이벤트 루프가 결과를 얻을 수 없지만 미래의 이벤트 루프가 결과를 제공한다는 것입니다. 멍청이가 말하는 것과 매우 유사합니다.
약속은 쓰레기일 뿐만 아니라 상태 기계이기도 합니다:
const pro = new Promise((resolve, Reject) => { setTimeout(() => { 해결('2') }, 200) }) console.log(pro) // 인쇄: Promise { <pending> }
then 또는 catch를 실행하면 새 Promise가 반환 됩니다. Promise의 최종 상태는 then 및 catch의 콜백 함수 실행 결과에 따라 결정됩니다.
함수 인터뷰() { return new Promise((해결, 거부) => { setTimeout(() => { if (Math.random() > 0.5) { 해결('성공') } 또 다른 { 거부(새 오류('실패')) } }) }) } var 약속 = 인터뷰() var promise1 = promise.then(() => { return new Promise((해결, 거부) => { setTimeout(() => { 해결('수락') }, 400) }) })
promise1의 상태는 반환된 promise의 상태, 즉 반환된 promise가 실행된 후 promise1의 상태에 따라 결정됩니다. 이것의 이점은 무엇입니까? 이것은 콜백 지옥의 문제를 해결합니다 .
var 약속 = 인터뷰() .then(() => { 복귀 인터뷰() }) .then(() => { 복귀 인터뷰() }) .then(() => { 복귀 인터뷰() }) .catch(e => { console.log(e) })
그런 다음 반환된 Promise의 상태가 거부되면 첫 번째 catch가 호출되고 후속 catch가 호출되지 않습니다. 기억하세요: 거부된 통화는 첫 번째 캐치이고, 해결된 통화는 그 다음 첫 번째입니다.
. Promise가 지옥 콜백만 해결하는 것이라면 Promise의 주요 기능은 비동기 프로세스 제어 문제를 해결하는 것입니다. 두 회사를 동시에 면접하고 싶은 경우:
function Interview() { return new Promise((해결, 거부) => { setTimeout(() => { if (Math.random() > 0.5) { 해결('성공') } 또 다른 { 거부(새 오류('실패')) } }) }) } 약속하다 .all([인터뷰(), 인터뷰()]) .then(() => { console.log('미소') }) // 회사가 거절하면 잡아라 .catch(() => { console.log('울음') })
sync/await가 정확히 무엇인가요:
console.log(async function() { 4를 반환 }) console.log(함수() { return new Promise((해결, 거부) => { 해결(4) }) })
인쇄된 결과는 동일합니다. 즉, async/await는 약속을 위한 구문적 설탕일 뿐입니다.
우리는 try catch가 호출 스택을 기반으로 오류를 캡처하고 호출 스택 위의 오류만 캡처할 수 있다는 것을 알고 있습니다. 하지만 Wait를 사용하면 호출 스택의 모든 함수에서 오류를 잡을 수 있습니다. setTimeout과 같은 다른 이벤트 루프의 호출 스택에서 오류가 발생하더라도 마찬가지입니다.
인터뷰 코드를 변환하고 나면 코드가 많이 간소화된 것을 확인할 수 있습니다.
노력하다 { 인터뷰를 기다리다(1) 인터뷰를 기다리다(2) 인터뷰를 기다리다(2) } 잡기(e => { console.log(e) })
병렬 작업이라면 어떻게 될까요?
wait Promise.all([interview(1), 인터뷰(2)])
nodejs의 비차단 I/0으로 인해 I/O 결과를 얻으려면 이벤트 기반 방법을 사용해야 합니다. 결과를 얻으려면 콜백 함수와 같은 비동기 프로그래밍을 사용해야 합니다. 그렇다면 결과를 얻기 위해 이러한 콜백 함수를 실행하는 방법은 무엇입니까? 그런 다음 이벤트 루프를 사용해야 합니다.
이벤트 루프는 nodejs의 Non-Blocking I/O 기능을 구현하기 위한 핵심 기반입니다. Non-Blocking I/O와 이벤트 루프는 C++ 라이브러리 libuv
에서 제공하는 기능입니다.
코드 데모:
const eventloop = { 대기줄: [], 루프() { 동안(this.queue.length) { const 콜백 = this.queue.shift() 콜백() } setTimeout(this.loop.bind(this), 50) }, 추가(콜백) { this.queue.push(콜백) } } 이벤트루프.loop() setTimeout(() => { eventloop.add(() => { console.log('1') }) }, 500) setTimeout(() => { eventloop.add(() => { console.log('2') }) }, 800)
setTimeout(this.loop.bind(this), 50)
은 50ms 후에 대기열에 콜백이 있는지 확인하고 콜백이 있으면 실행하도록 합니다. 이는 이벤트 루프를 형성합니다.
물론 실제 이벤트는 훨씬 더 복잡하며 큐가 두 개 이상 있습니다. 예를 들어 파일 작업 큐와 시간 큐가 있습니다.
const 이벤트루프 = { 대기줄: [], fs큐: [], 타이머큐: [], 루프() { 동안(this.queue.length) { const 콜백 = this.queue.shift() 콜백() } this.fsQueue.forEach(콜백 => { 만약 (완료) { 콜백() } }) setTimeout(this.loop.bind(this), 50) }, 추가(콜백) { this.queue.push(콜백) } }
우선, 우리는 non-blocking I/O가 무엇인지 이해합니다. 즉, I/O를 만나면 즉시 후속 작업 실행을 건너뛰고 I/O 결과를 기다리지 않습니다. I/O가 처리되면 우리가 등록한 이벤트 처리 함수가 호출되는데, 이를 이벤트 기반이라고 합니다. 이벤트 구동을 구현하려면 비동기 프로그래밍이 필요합니다. 비동기 프로그래밍은 nodejs에서 가장 중요한 링크입니다. 콜백 함수에서 Promise로, 마지막으로 async/await로 이동합니다(비동기 로직을 작성하기 위해 동기 메서드 사용).