비동기 계산을 수행하는 방법은 무엇입니까? 다음 글에서는 브라우저와 Node.js의 멀티스레딩 기능을 사용하여 비동기 계산을 수행하는 방법을 소개하겠습니다. 도움이 되기를 바랍니다.
Node.js가 고성능 서버를 구현할 수 있다고 하는데 고성능이란 무엇일까요?
모든 소프트웨어 코드는 궁극적으로 CPU를 통해 실행됩니다. CPU를 효율적으로 활용할 수 있는지 여부는 성능의 표시이며, 이는 CPU가 유휴 상태가 될 수 없음을 의미합니다. [추천 학습: "nodejs tutorial"]
그럼 언제 유휴 상태가 되나요?
그래서 고성능을 달성하려면 이 두 가지 문제를 해결해야 합니다.
운영 체제는 스레드의 추상화를 제공합니다. 코드에 해당하는 다양한 실행 분기가 동시에 여러 CPU에서 실행될 수 있습니다. 이는 멀티 코어 CPU의 성능을 활용하는 방법입니다.
일부 스레드가 IO를 수행하는 경우 읽기 및 쓰기가 완료될 때까지 차단하고 기다려야 합니다. 이는 상대적으로 비효율적인 방법이므로 운영 체제가 장치 컨트롤러인 DMA 메커니즘을 구현하고 하드웨어가 책임을 집니다. 장치를 메모리로 이동할 때 이동이 완료되면 CPU에 알립니다. 이러한 방식으로 일부 스레드가 IO를 수행할 때 DMA 전송 데이터가 완료되었다는 알림을 받은 후 스레드를 일시 중지하고 계속 실행할 수 있습니다.
멀티 스레딩 및 DMA는 멀티 코어 CPU를 활용하고 CPU 차단과 같은 IO 문제를 해결하는 운영 체제에서 제공하는 솔루션입니다.
다양한 프로그래밍 언어가 이 메커니즘을 캡슐화했으며 Node.js도 동일한 기능을 수행합니다. Node.js가 고성능인 이유는 비동기식 IO 설계 때문입니다.
Node.js의 비동기 IO는 운영 체제에서 제공하는 비동기 시스템 호출을 기반으로 libuv에서 구현됩니다. 이는 일반적으로 데이터 전송을 위한 DMA와 같은 하드웨어 수준 비동기입니다. 그러나 일부 동기 시스템 호출은 libuv에 의해 캡슐화된 후 비동기화됩니다. 이는 libuv에 이러한 작업을 수행하고 동기 API를 비동기화로 전환하는 스레드 풀이 있기 때문입니다. 이 스레드 풀의 크기는 UV_THREADPOOL_SIZE
환경 변수를 통해 설정할 수 있으며 기본값은 4입니다.
우리 코드에서 호출하는 많은 비동기 API는 스레드를 통해 구현됩니다.
예:
const fsPromises = require('fs').promises; const data = await fsPromises.readFile('./filename');
그러나 이 비동기 API는 IO 문제만 해결하는데 멀티 코어 CPU를 계산에 활용하는 방법은 무엇입니까?
Node.js는 10.5(12에서 공식적으로 도입됨)에서 스레드를 생성하고 궁극적으로 여러 CPU에서 실행할 수 있는 work_thread 모듈을 사용하여 실험적으로 도입되었습니다. 이는 계산에 멀티 코어 CPU를 사용하는 방법입니다.
비동기 API는 다중 스레드를 사용하여 IO를 수행할 수 있으며, Worker_thread는 스레드를 생성하여 다양한 목적으로 계산을 수행할 수 있습니다.
worker_thread에 대해 명확하게 설명하려면 브라우저의 웹 워커부터 시작해야 합니다.
브라우저 역시 계산에 멀티 코어 CPU를 사용할 수 없다는 문제에 직면해 있습니다. 그래서 html5에서는 다른 스레드를 통해 계산을 수행할 수 있는 웹 워커를 도입했습니다.
<!DOCTYPE html> <html> <head></head> <body> <script> (async function () { const res = await runCalcWorker(2, 3, 3, 3); console.log(res); })(); function runCalcWorker(...nums) { return new Promise((resolve, reject) => { const calcWorker = new Worker('./webWorker.js'); calcWorker.postMessage(nums) calcWorker.onmessage = function (msg) { resolve(msg.data); }; calcWorker.onerror = reject; }); } </script> </body> </html>
Worker 객체를 생성하고 다른 스레드에서 실행되는 js 코드를 지정한 다음 postMessage를 통해 메시지를 전달하고 onMessage를 통해 메시지를 받습니다. 이 프로세스도 비동기식이므로 이를 Promise로 캡슐화합니다.
그런 다음 webWorker.js에서 데이터를 수신하고 계산을 수행한 다음 postMessage를 통해 결과를 반환합니다.
// webWorker.js onmessage = function(msg) { if (Array.isArray(msg.data)) { const res = msg.data.reduce((total, cur) => { return total += cur; }, 0); postMessage(res); } }
이러한 방식으로 다른 CPU 코어를 사용하여 이 계산을 실행합니다. 코드 작성은 일반 비동기 코드와 다르지 않습니다. 그러나 이 비동기는 실제로 IO 비동기가 아니라 계산 비동기입니다.
Node.js의 작업자 스레드는 웹 작업자와 유사합니다. 작업자 스레드의 이름이 웹 작업자의 영향을 받는 것까지 의심됩니다.
위 비동기 계산 논리를 Node.js에서 구현하면 다음과 같습니다.
const runCalcWorker = require('./runCalcWorker'); (async function () { const res = await runCalcWorker(2, 3, 3, 3); console.log(res); })();
비동기 계산과 비동기 IO가 동일하게 사용되므로 비동기 방식으로 호출합니다. 방법에는 차이가 없습니다.
// runCalcWorker.js const { Worker } = require('worker_threads'); module.exports = function(...nums) { return new Promise(function(resolve, reject) { const calcWorker = new Worker('./nodeWorker.js'); calcWorker.postMessage(nums); calcWorker.on('message', resolve); calcWorker.on('error', reject); }); }
그런 다음 Worker 객체를 생성하고 JS를 다른 스레드에서 실행하도록 지정한 다음 postMessage를 통해 메시지를 전달하고 message를 통해 메시지를 수신하는 방식으로 비동기 계산이 구현됩니다. 이는 웹 워커와 매우 유사합니다.
// nodeWorker.js const { parentPort } = require('worker_threads'); parentPort.on('message', (data) => { const res = data.reduce((total, cur) => { return total += cur; }, 0); parentPort.postMessage(res); });
계산을 구체적으로 수행하는 nodeWorker.js에서는 메시지 메시지를 듣고 계산을 수행한 후 parentPost.postMessage를 통해 데이터를 반환합니다.
웹워커를 비교해 보면 특별한 유사점을 발견할 수 있습니다. 따라서 Node.js의 작업자 스레드 API는 웹 작업자를 참조하여 설계되었다고 생각합니다.
그러나 실제로 작업자 스레드는 생성 시 WorkerData를 통한 데이터 전달도 지원합니다.
const { Worker } = require('worker_threads'); module.exports = function(...nums) { return new Promise(function(resolve, reject) { const calcWorker = new Worker('./nodeWorker.js', { workerData: nums }); calcWorker.on('message', resolve); calcWorker.on('error', reject); }); }
그런 다음 작업자 스레드는 작업자 데이터를 통해 데이터를 검색할 수 있습니다.
const { parentPort, workerData } = require('worker_threads'); const data = workerData; const res = data.reduce((total, cur) => { return total += cur; }, 0); parentPort.postMessage(res);
因为有个传递消息的机制,所以要做序列化和反序列化,像函数这种无法被序列化的数据就无法传输了。这也是 worker thread 的特点。
从使用上来看,都可以封装成普通的异步调用,和其他异步 API 用起来没啥区别。
都要经过数据的序列化反序列化,都支持 postMessage、onMessage 来收发消息。
除了 message,Node.js 的 worker thread 支持传递数据的方式更多,比如还有 workerData。
但从本质上来看,两者都是为了实现异步计算,充分利用多核 CPU 的性能,没啥区别。
高性能的程序也就是要充分利用 CPU 资源,不要让它空转,也就是 IO 的时候不要让 CPU 等,多核 CPU 也要能同时利用起来做计算。操作系统提供了线程、DMA的机制来解决这种问题。Node.js 也做了相应的封装,也就是 libuv 实现的异步 IO 的 api,但是计算的异步是 Node 12 才正式引入的,也就是 worker thread,api 设计参考了浏览器的 web worker,传递消息通过 postMessage、onMessage,需要做数据的序列化,所以函数是没法传递的。
从使用上来看异步计算、异步 IO 使用方式一样,但是异步 IO 只是让 cpu 不同阻塞的等待 IO 完成,异步计算是利用了多核 CPU 同时进行并行的计算,数倍提升计算性能。
更多编程相关知识,请访问:编程视频!!
위 내용은 Node.js의 멀티스레딩 기능을 사용하여 비동기 계산을 수행하는 방법에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!