搜索
首页web前端js教程进程和线程如何理解?Node.js中的进程和线程是怎样的?

线程和进程是计算机操作系统的基础概念,在程序员中属于高频词汇,那如何理解呢?Node中的进程和线程又是怎样的呢?下面本篇文章就来一起了解一下,希望对大家有所帮助!

进程和线程如何理解?Node.js中的进程和线程是怎样的?

一、进程和线程

1.1、专业性文字定义

  • 进程(Process),进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是线程的容器。
  • 线程(Thread),线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。

1.2、通俗理解

以上描述比较硬,看完可能也没看懂,还不利于理解记忆。那么我们举个简单的例子:

假设你是某个快递站点的一名小哥,起初这个站点负责的区域住户不多,收取件都是你一个人。给张三家送完件,再去李四家取件,事情得一件件做,这叫单线程,所有的工作都得按顺序执行

后来这个区域住户多了,站点给这个区域分配了多个小哥,还有个小组长,你们可以为更多的住户服务了,这叫多线程,小组长是主线程,每个小哥都是一个线程

快递站点使用的小推车等工具,是站点提供的,小哥们都可以使用,并不仅供某一个人,这叫多线程资源共享。

站点小推车目前只有一个,大家都需要使用,这叫冲突。解决的方法有很多,排队等待或者等其他小哥用完后的通知,这叫线程同步

file

总公司有很多站点,各个站点的运营模式几乎一模一样,这叫多进程。总公司叫主进程,各个站点叫子进程
总公司和站点之间,以及各个站点互相之间,小推车都是相互独立的,不能混用,这叫进程间不共享资源。各站点间可以通过电话等方式联系,这叫管道。各站点间还有其他协同手段,便于完成更大的计算任务,这叫进程间同步

还可以看看阮一峰的 进程与线程的一个简单解释。

二、Node.js 中的进程和线程

Node.js 是单线程服务,事件驱动和非阻塞 I/O 模型的语言特性,使得 Node.js 高效和轻量。优势在于免去了频繁切换线程和资源冲突;擅长 I/O 密集型操作(底层模块 libuv 通过多线程调用操作系统提供的异步 I/O 能力进行多任务的执行),但是对于服务端的 Node.js,可能每秒有上百个请求需要处理,当面对 CPU 密集型请求时,因为是单线程模式,难免会造成阻塞。

2.1、Node.js 阻塞

我们利用 Koa 简单地搭建一个 Web 服务,用斐波那契数列方法来模拟一下 Node.js 处理 CPU 密集型的计算任务:

斐波那契数列,也称黄金分割数列,这个数列从第三项开始,每一项都等于前两项只和: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')
})

执行 node app.js 启动服务,用 Postman 发送请求,可以看到,计算 38 次耗费了 617ms,换而言之,因为执行了一个 CPU 密集型的计算任务,所以 Node.js 主线程被阻塞了六百多毫秒。如果同时处理更多的请求,或者计算任务更复杂,那么在这些请求之后的所有请求都会被延迟执行。

file

我们再新建一个 axios.js 用来模拟发送多次请求,此时将 app.js 中的 fibo 计算次数改为 43,用来模拟更复杂的计算任务:

// 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')

file

可以看到,当请求需要执行 CPU 密集型的计算任务时,后续的请求都被阻塞等待,这类请求一多,服务基本就阻塞卡死了。对于这种不足,Node.js 一直在弥补。

2.2、master-worker

master-worker 模式是一种并行模式,核心思想是:系统有两个及以上的进程或线程协同工作时,master 负责接收和分配并整合任务,worker 负责处理任务。

file

2.3、多线程

线程是 CPU 调度的一个基本单位,只能同时执行一个线程的任务,同一个线程也只能被一个 CPU 调用。如果使用的是多核 CPU,那么将无法充分利用 CPU 的性能。

多线程带给我们灵活的编程方式,但是需要学习更多的 Api 知识,在编写更多代码的同时也存在着更多的风险,线程的切换和锁也会增加系统资源的开销。

  • worker_threads 工作线程,给 Node.js 提供了真正的多线程能力。

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,用来模拟更复杂的计算任务:

file

可以看到,将 CPU 密集型的计算任务交给子线程处理时,主线程不再被阻塞,只需等待子线程处理完成后,主线程接收子线程返回的结果即可,其他请求不再受影响。
上述代码是演示创建 worker 线程的过程和效果,实际开发中,请使用线程池来代替上述操作,因为频繁创建线程也会有资源的开销。

线程是 CPU 调度的一个基本单位,只能同时执行一个线程的任务,同一个线程也只能被一个 CPU 调用。

我们再回味下,本小节开头提到的线程和 CPU 的描述,此时由于是新的线程,可以在其他 CPU 核心上执行,可以更充分的利用多核 CPU。

2.4、多进程

Node.js 为了能充分利用 CPU 的多核能力,提供了 cluster 模块,cluster 可以通过一个父进程管理多个子进程的方式来实现集群的功能。

  • child_process 子进程,衍生新的 Node.js 进程并使用建立的 IPC 通信通道调用指定的模块。
  • cluster 集群,可以创建共享服务器端口的子进程,工作进程使用 child_process 的 fork 方法衍生。

cluster 底层就是 child_process,master 进程做总控,启动 1 个 agent 进程和 n 个 worker 进程,agent 进程处理一些公共事务,比如日志等;worker 进程使用建立的 IPC(Inter-Process Communication)通信通道和 master 进程通信,和 master 进程共享服务端口。

file

新增 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 < 10; i++) {
    axios.get(url).then((res) => {
        console.log(res.data, `耗时: ${ Date.now() - start }ms`)
    })
}

可以看到,只使用了一个进程,10 个请求慢慢阻塞,累计耗时 15 秒:

file

接下来,将 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 < numCPUs; i++) {
        cluster.fork()
    }

    cluster.on(&#39;exit&#39;, (worker, code, signal) => {
        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 进程:

file

file

通过 fibo-10.js  模拟发送 10 次请求,可以看到,四个进程处理 10 个请求耗时近 9 秒:

file

当启动 10 个 worker 进程时,看看效果:

file

仅需不到 3 秒,不过进程的数量也不是无限的。在日常开发中,worker 进程的数量一般和 CPU 核心数相同。

2.5、多进程说明

开启多进程不全是为了处理高并发,而是为了解决 Node.js 对于多核 CPU 利用率不足的问题。
由父进程通过 fork 方法衍生出来的子进程拥有和父进程一样的资源,但是各自独立,互相之间资源不共享。通常根据 CPU 核心数来设置进程数量,因为系统资源是有限的。

三、总结

1、大部分通过多线程解决 CPU 密集型计算任务的方案都可以通过多进程方案来替代;
2、Node.js 虽然异步,但是不代表不会阻塞,CPU 密集型任务最好不要在主线程处理,保证主线程的畅通;
3、不要一味的追求高性能和高并发,达到系统需要即可,高效、敏捷才是项目需要的,这也是 Node.js 轻量的特点。
4、Node.js 中的进程和线程还有很多概念在文章中提到了但没展开细讲或没提到的,比如:Node.js 底层 I/O 的 libuv、IPC 通信通道、多进程如何守护、进程间资源不共享如何处理定时任务、agent 进程等;
5、以上代码可在 https://github.com/liuxy0551/node-process-thread 查看。

更多node相关知识,请访问:nodejs 教程

以上是进程和线程如何理解?Node.js中的进程和线程是怎样的?的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:segmentfault。如有侵权,请联系admin@php.cn删除
8核16线程是什么意思?8核16线程是什么意思?Feb 02, 2023 am 11:26 AM

8核是指CPU有8颗物理核心,16线程是指CPU最多同时可以有16个线程处理任务。核心数和线程数是电脑CPU的重要性能指标,CPU的核心数越高处理速度就越高;线程数越多越有利于同时运行多个程序,因为线程数等同于在某个瞬间CPU能同时并行处理的任务数。多线程可最大限度地实现宽发射、乱序的超标量处理,提高处理器运算部件的利用率,缓和由于数据相关或Cache未命中带来的访问内存延时。

Java错误:JavaFX线程卡顿错误,如何处理和避免Java错误:JavaFX线程卡顿错误,如何处理和避免Jun 24, 2023 pm 05:52 PM

在进行JavaFX应用程序开发的过程中,我们常常会遇到JavaFX线程卡顿错误。这种错误的严重程度不同,可能会对程序的稳定性和性能产生不利的影响。为了保证程序的正常运行,我们需要了解JavaFX线程卡顿错误的原因和解决方法,以及如何预防这种错误的发生。一、JavaFX线程卡顿错误的原因JavaFX是一个多线程的UI应用程序框架,它允许程序在后台线程中执行长时

什么是程序运行时指令流的最小单位什么是程序运行时指令流的最小单位Aug 23, 2022 pm 02:16 PM

“线程”是程序运行时指令流的最小单位。进程是指一个具有一定独立功能的程序,而线程是进程的一部分,描述指令流执行状态;线程是进程中的指令执行流的最小单位,是CPU调度的基本单位。一个线程是一个任务(一个程序段)的一次执行过程;线程不占有内存空间,它包括在进程的内存空间中。在同一个进程内,多个线程共享进程的资源;一个进程至少有一个线程。

Go语言中线程和进程的区别解析Go语言中线程和进程的区别解析Apr 03, 2024 pm 01:39 PM

Go语言中的进程和线程:进程:独立运行的程序实例,拥有自己的资源和地址空间。线程:进程内的执行单元,共享进程资源和地址空间。特点:进程:开销大,隔离性好,独立调度。线程:开销小,共享资源,内部调度。实战案例:进程:隔离长时间运行的任务。线程:并发处理大量数据。

go语言中协程与线程的区别是什么go语言中协程与线程的区别是什么Feb 02, 2023 pm 06:10 PM

区别:1、一个线程可以多个协程,一个进程也可以单独拥有多个协程;2、线程是同步机制,而协程则是异步;3、协程能保留上一次调用时的状态,线程不行;4、线程是抢占式,协程是非抢占式的;5、线程是被分割的CPU资源,协程是组织好的代码流程,协程需要线程来承载运行。

我们如何在Java中实现一个计时器线程?我们如何在Java中实现一个计时器线程?Aug 30, 2023 pm 02:49 PM

Timer类安排任务在给定时间运行一次或重复。它还可以作为守护线程在后台运行。要将Timer与守护线程关联起来,需要使用一个带有布尔值的构造函数。计时器以固定延迟和固定速率安排任务。在固定延迟下,如果任何一个执行被系统GC延迟,则其他执行也会延迟,并且每次执行都会延迟对应于之前的执行。在固定速率下,如果任何执行被系统GC延迟,则连续发生2-3次执行以覆盖与第一次执行开始时间相对应的固定速率。Timer类提供了cancel()方法来取消计时器。当调用该方法时,定时器终止。Timer类仅执行实现Ti

Java使用Thread类的stop()函数强制终止线程的执行Java使用Thread类的stop()函数强制终止线程的执行Jul 26, 2023 am 09:28 AM

Java使用Thread类的stop()函数强制终止线程的执行在Java多线程编程中,有时候我们需要强制终止一个正在执行的线程。Java提供了Thread类的stop()函数来实现线程的强制终止。本文将介绍stop()函数的用法,并提供代码示例来说明。在介绍stop()函数之前,我们先了解一下Thread类的几个常用方法:start():启动线程,使线程进入

Microsoft计划在Windows上的Outlook经典应用程序中引入AI驱动的CopilotMicrosoft计划在Windows上的Outlook经典应用程序中引入AI驱动的CopilotOct 19, 2023 pm 11:13 PM

Microsoft显然不会将其强大的人工智能支持的Copilot工具保留为新应用程序的独家功能。现在,该公司刚刚宣布计划在Windows上的Outlook经典应用程序中引入Copilot。正如其365路线图网站上发布的那样,预览将于明年&lt;&gt;月开始,直到&lt;&gt;月在当前频道的桌面上在全球范围内推出。Copilot是一种生产力工具,它使用大型语言模型(LLM)来帮助用户完成编写电子邮件、汇总文档和翻译语言等任务。它的主要功能之一是它能够总结电子邮件

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!