Les threads et les processus sont les concepts de base des systèmes d'exploitation informatiques et sont des mots très fréquents parmi les programmeurs. Alors, comment les comprenez-vous ? Qu’en est-il des processus et des threads dans Node.js ? L'article suivant vous donnera une compréhension approfondie des processus et des threads dans Node. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il sera utile à tout le monde.
La description ci-dessus est relativement difficile. Vous ne la comprendrez peut-être pas après l'avoir lue, et elle n'est pas propice à la compréhension et à la mémoire. Prenons donc un exemple simple :
Supposons que vous soyez un gars d'un certain site de livraison express. Au début, la zone dont ce site est responsable n'a pas beaucoup d'habitants et vous êtes le seul à récupérer les colis. Après avoir livré le colis chez Zhang San, puis aller le chercher chez Li Si, les choses doivent être faites une par une, c'est ce qu'on appelle fil unique, et tout le travail doit être effectué dans l'ordre.
Plus tard, il y avait plus de résidents dans cette zone, et le site a affecté plusieurs gars et un chef d'équipe à cette zone. Vous pouvez servir plus de résidents. C'est ce qu'on appelle le multi-threading, et le chef d'équipe est le thread principal. Chaque gars est un thread.
Les outils tels que les chariots utilisés par les sites de livraison express sont fournis par le site et peuvent être utilisés par tout le monde, pas seulement par une seule personne. C'est ce qu'on appelle le partage de ressources multithread.
Il n'y a actuellement qu'un seul panier de site et tout le monde doit l'utiliser. C'est ce qu'on appelle conflit. Il existe de nombreuses façons de résoudre ce problème, comme faire la queue ou attendre des notifications lorsque d'autres gars ont terminé. C'est ce qu'on appelle la synchronisation des threads.
Le siège social compte de nombreux sites, et le modèle de fonctionnement de chaque site est presque exactement le même. C'est ce qu'on appelle le multi-processus. Le siège social est appelé processus principal, et chaque site est appelé sous-processus.
Entre le siège social et le site, ainsi qu'entre chaque site, les chariots sont indépendants les uns des autres et ne peuvent être mélangés. C'est ce qu'on appelle pas de partage de ressources entre processus. Chaque site peut communiquer entre eux via des appels téléphoniques, etc. C'est ce qu'on appelle un pipeline. Il existe d'autres moyens de collaboration entre les sites pour faciliter la réalisation de tâches informatiques plus importantes, appelés synchronisation inter-processus.
Vous pouvez également jeter un œil à Une explication simple des processus et des threads de Ruan Yifeng.
Node.js est un service monothread, piloté par les événements et doté de fonctionnalités de langage de modèle d'E/S non bloquantes, rendant Node.js efficace et léger. L'avantage est qu'il évite les changements de thread fréquents et les conflits de ressources ; il est efficace pour les opérations gourmandes en E/S (le module sous-jacent libuv effectue le multitâche en appelant les capacités d'E/S asynchrones fournies par le système d'exploitation via plusieurs threads). , mais pour Node.js côté serveur, il peut y avoir des centaines de requêtes qui doivent être traitées par seconde. Face à des requêtes gourmandes en CPU, car il s'agit d'un mode monothread, cela provoquera inévitablement un blocage.
Nous utilisons Koa pour simplement créer un service Web et utilisons la méthode de séquence de Fibonacci pour simuler le traitement par Node.js de tâches informatiques gourmandes en CPU :
Fibonacci Une séquence de nombres, également connue comme séquence du nombre d'or. Cette séquence commence par le troisième élément, et chaque élément est égal à la somme des deux éléments précédents : 0, 1, 1, 2, 3, 5, 8, 13, 21,... .
// app.js const Koa = require('koa') const router = require('koa-router')() const app = new Koa() // 用来测试是否被阻塞 router.get('/test', (ctx) => { ctx.body = { pid: process.pid, msg: 'Hello World' } }) router.get('/fibo', (ctx) => { const { num = 38 } = ctx.query const start = Date.now() // 斐波那契数列 const fibo = (n) => { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1 } fibo(num) ctx.body = { pid: process.pid, duration: Date.now() - start } }) app.use(router.routes()) app.listen(9000, () => { console.log('Server is running on 9000') })
Exécutionnode app.js
Démarrez le service et utilisez Postman pour envoyer la requête. Vous pouvez voir que 38 calculs ont pris 617 ms. En d'autres termes, parce qu'une tâche informatique gourmande en CPU a été effectuée, le thread principal de Node.js a été bloqué. que six cents millisecondes. Si plusieurs demandes sont traitées en même temps ou si la tâche de calcul est plus complexe, toutes les demandes ultérieures seront retardées.
Créons un nouveau axios.js pour simuler l'envoi de plusieurs requêtes. À ce stade, modifions le nombre de calculs fibo dans app.js à 43 pour simuler des tâches informatiques plus complexes :
// axios.js const axios = require('axios') const start = Date.now() const fn = (url) => { axios.get(`http://127.0.0.1:9000/${ url }`).then((res) => { console.log(res.data, `耗时: ${ Date.now() - start }ms`) }) } fn('test') fn('fibo?num=43') fn('test')
可以看到,当请求需要执行 CPU 密集型的计算任务时,后续的请求都被阻塞等待,这类请求一多,服务基本就阻塞卡死了。对于这种不足,Node.js 一直在弥补。
master-worker 模式是一种并行模式,核心思想是:系统有两个及以上的进程或线程协同工作时,master 负责接收和分配并整合任务,worker 负责处理任务。
线程是 CPU 调度的一个基本单位,只能同时执行一个线程的任务,同一个线程也只能被一个 CPU 调用。如果使用的是多核 CPU,那么将无法充分利用 CPU 的性能。
多线程带给我们灵活的编程方式,但是需要学习更多的 Api 知识,在编写更多代码的同时也存在着更多的风险,线程的切换和锁也会增加系统资源的开销。
worker_threads 是 Node.js 提供的一种多线程 Api。对于执行 CPU 密集型的计算任务很有用,对 I/O 密集型的操作帮助不大,因为 Node.js 内置的异步 I/O 操作比 worker_threads 更高效。worker_threads 中的 Worker,parentPort 主要用于子线程和主线程的消息交互。
将 app.js 稍微改动下,将 CPU 密集型的计算任务交给子线程计算:
// app.js const Koa = require('koa') const router = require('koa-router')() const { Worker } = require('worker_threads') const app = new Koa() // 用来测试是否被阻塞 router.get('/test', (ctx) => { ctx.body = { pid: process.pid, msg: 'Hello World' } }) router.get('/fibo', async (ctx) => { const { num = 38 } = ctx.query ctx.body = await asyncFibo(num) }) const asyncFibo = (num) => { return new Promise((resolve, reject) => { // 创建 worker 线程并传递数据 const worker = new Worker('./fibo.js', { workerData: { num } }) // 主线程监听子线程发送的消息 worker.on('message', resolve) worker.on('error', reject) worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)) }) }) } app.use(router.routes()) app.listen(9000, () => { console.log('Server is running on 9000') })
新增 fibo.js 文件,用来处理复杂计算任务:
const { workerData, parentPort } = require('worker_threads') const { num } = workerData const start = Date.now() // 斐波那契数列 const fibo = (n) => { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1 } fibo(num) parentPort.postMessage({ pid: process.pid, duration: Date.now() - start })
执行上文的 axios.js,此时将 app.js 中的 fibo 计算次数改为 43,用来模拟更复杂的计算任务:
可以看到,将 CPU 密集型的计算任务交给子线程处理时,主线程不再被阻塞,只需等待子线程处理完成后,主线程接收子线程返回的结果即可,其他请求不再受影响。
上述代码是演示创建 worker 线程的过程和效果,实际开发中,请使用线程池来代替上述操作,因为频繁创建线程也会有资源的开销。
线程是 CPU 调度的一个基本单位,只能同时执行一个线程的任务,同一个线程也只能被一个 CPU 调用。
我们再回味下,本小节开头提到的线程和 CPU 的描述,此时由于是新的线程,可以在其他 CPU 核心上执行,可以更充分的利用多核 CPU。
Node.js 为了能充分利用 CPU 的多核能力,提供了 cluster 模块,cluster 可以通过一个父进程管理多个子进程的方式来实现集群的功能。
cluster 底层就是 child_process,master 进程做总控,启动 1 个 agent 进程和 n 个 worker 进程,agent 进程处理一些公共事务,比如日志等;worker 进程使用建立的 IPC(Inter-Process Communication)通信通道和 master 进程通信,和 master 进程共享服务端口。
新增 fibo-10.js,模拟发送 10 次请求:
// fibo-10.js const axios = require('axios') const url = `http://127.0.0.1:9000/fibo?num=38` const start = Date.now() for (let i = 0; i { console.log(res.data, `耗时: ${ Date.now() - start }ms`) }) }
可以看到,只使用了一个进程,10 个请求慢慢阻塞,累计耗时 15 秒:
接下来,将 app.js 稍微改动下,引入 cluster 模块:
// app.js const cluster = require('cluster') const http = require('http') const numCPUs = require('os').cpus().length // const numCPUs = 10 // worker 进程的数量一般和 CPU 核心数相同 const Koa = require('koa') const router = require('koa-router')() const app = new Koa() // 用来测试是否被阻塞 router.get('/test', (ctx) => { ctx.body = { pid: process.pid, msg: 'Hello World' } }) router.get('/fibo', (ctx) => { const { num = 38 } = ctx.query const start = Date.now() // 斐波那契数列 const fibo = (n) => { return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1 } fibo(num) ctx.body = { pid: process.pid, duration: Date.now() - start } }) app.use(router.routes()) if (cluster.isMaster) { console.log(`Master ${process.pid} is running`) // 衍生 worker 进程 for (let i = 0; i { console.log(`worker ${worker.process.pid} died`) }) } else { app.listen(9000) console.log(`Worker ${process.pid} started`) }
执行 node app.js
启动服务,可以看到,cluster 帮我们创建了 1 个 master 进程和 4 个 worker 进程:
通过 fibo-10.js 模拟发送 10 次请求,可以看到,四个进程处理 10 个请求耗时近 9 秒:
Lorsque 10 processus de travail sont démarrés, regardez l'effet :
Cela ne prend que moins de 3 secondes, mais le nombre de processus n'est pas illimité. Dans le développement quotidien, le nombre de processus de travail est généralement le même que le nombre de cœurs de processeur.
L'activation du multi-processus ne vise pas uniquement à gérer une concurrence élevée, mais à résoudre le problème de l'utilisation insuffisante du processeur multicœur de Node.js.
Le processus enfant dérivé du processus parent via la méthode fork a les mêmes ressources que le processus parent, mais ils sont indépendants et ne partagent pas de ressources entre eux. Le nombre de processus est généralement défini en fonction du nombre de cœurs de processeur car les ressources système sont limitées.
1. La plupart des solutions permettant de résoudre des tâches informatiques gourmandes en CPU via le multi-threading peuvent être remplacées par des solutions multi-processus ;
2. Bien que Node.js soit asynchrone, cela ne signifie pas qu'il le sera ; ne bloquez pas le processeur. Il est préférable de ne pas traiter de tâches intensives dans le thread principal pour assurer le bon fonctionnement du thread principal
3. Ne recherchez pas aveuglément des performances élevées et une concurrence élevée, répondez simplement aux besoins du système. et l'agilité sont ce dont le projet a besoin, et ce sont également les fonctionnalités légères de Node.js.
4. Il existe de nombreux concepts de processus et de threads dans Node.js qui sont mentionnés dans l'article mais ne sont pas abordés en détail ou ne sont pas mentionnés, tels que : libuv, canal de communication IPC, multi-processus de Node.js sous-jacent à I/. O Comment gérer les tâches planifiées, les processus d'agent, etc. lorsque les ressources entre les démons et les processus ne sont pas partagées
5 Le code ci-dessus peut être consulté sur https://github.com/liuxy0551/node-process-thread.
Pour plus de connaissances sur les nœuds, veuillez visiter : tutoriel Nodejs !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!