【JavaScript】 线程和进程

JavaScript 的单线程

此外,JS 最初是为了解决⽹⻚交互的问题⽽诞⽣的,⽽⽹⻚交互的需求⼤部分是基于⽤户事件的,⽐如点击按钮、输⼊⽂本等。这些操作的响应速度要求很⾼,如果在响应事件的同时还要处理其他任务,可能会导致⽹⻚卡顿、响应变慢等⽤户体验不佳的问题。

为了利⽤多核 CPU 的计算能⼒,HTML5 提出 Web Worker 标准,允许 JS 脚本创建多个线程,但是⼦线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JS 单线程的本质。

线程与进程的区别

1. 进程(Process)
  • 定义:进程是一个程序在其运行过程中分配到的资源的集合。每个运行的程序都有自己的进程。
  • 特点
    • 独立性:进程是系统中资源分配和管理的基本单位。每个进程拥有独立的地址空间,进程之间是互相独立的。
    • 资源消耗大:启动一个新进程需要分配系统资源,如内存、CPU等,代价较大。
    • 隔离性强:进程之间不能直接共享内存,需要通过进程间通信(IPC)来交换数据。
2. 线程(Thread)
  • 定义:线程是进程中的一个执行单元,它是比进程更小的单位,属于进程的一部分。
  • 特点
    • 共享进程资源:同一个进程中的多个线程共享进程的内存和资源(如堆内存、全局变量等)。
    • 切换成本低:线程的创建、切换和销毁比进程快,因为线程间共享进程的资源,不需要重新分配资源。
    • 轻量级:相比进程,线程的资源消耗较少,系统切换线程的开销较小。

JavaScript中的线程与进程

JavaScript 是一种单线程的语言,这意味着它只能在一个时间点执行一个任务。理解 JavaScript 中的单线程模型需要结合事件循环(Event Loop)异步任务的概念。

1. 单线程模型
  • 主线程:JavaScript 的运行依赖于一个主线程,它负责执行所有的同步代码。由于它是单线程的,这意味着在任何时刻,JavaScript 只能处理一个任务。
  • 事件队列:当遇到异步任务时,JavaScript 会将这些任务根据环境的不同交给浏览器或 Node.js 环境来处理,并将回调函数放入事件队列中。当主线程空闲时,事件循环机制会从队列中取出任务并执行。
2. 异步与事件循环

JavaScript 通过事件循环来处理异步操作,像 setTimeoutPromiseI/O 操作 等。

  • 示例
console.log("Start");

setTimeout(() => {
  console.log("This is an async task");
}, 1000);

console.log("End");

在这个例子中,虽然 setTimeout 指定了 1 秒后执行的任务,但由于 JavaScript 是单线程的,它不会阻塞主线程继续执行后面的代码。setTimeout 任务会被放入事件队列中,当主线程执行完所有同步代码后,事件循环会检查队列,发现有任务需要执行,然后才执行它。

3. Web Workers(多线程)

虽然 JavaScript 本身是单线程的,但通过 Web Workers,可以在浏览器中创建额外的线程,用于处理一些计算密集型任务。

比如在大文件上传过程中的 hash 计算部分,由于需要读取所有文件,依次将每一个分片计算 hash ,是 CPU 密集型应用,直接在主线程操作会造成页面卡顿,所以需要开启额外的 线程来计算 hash 。

  • Web Workers 的特点

    • 运行在与主线程不同的上下文中,不能直接访问 DOM。
    • 通常用于处理大规模计算或需要长时间运行的任务。
    • 主线程和 Web Worker 通过消息传递来进行通信。
  • Web Worker 示例

// main.js
const worker = new Worker('worker.js');

worker.postMessage('Start heavy task');

worker.onmessage = (event) => {
  console.log('Received from worker:', event.data);
};

// worker.js
onmessage = (event) => {
  console.log('Worker received:', event.data);
  
  // 模拟计算密集型任务
  let result = 0;
  for (let i = 0; i < 1e9; i++) {
    result += i;
  }
  
  postMessage(result);
};

在这个例子中,main.js 创建了一个 Web Worker 来处理计算密集型任务。Worker 在线程中独立运行,不会阻塞主线程的 UI 渲染或其他操作。

4. Node.js 中的线程与进程

Node.js 是基于 JavaScript 的服务器端环境,虽然 JavaScript 是单线程的,但 Node.js 提供了多进程和多线程处理能力:

  • child_process 模块:可以使用多进程来处理多个任务。例如,创建一个子进程来处理大规模的任务,主进程与子进程之间通过 IPC 通信。

    • 示例
    const { fork } = require('child_process');
    
    const child = fork('child.js');
    
    child.send('Start task');
    
    child.on('message', (message) => {
      console.log('Message from child:', message);
    });
    
  • worker_threads 模块:Node.js 提供了 worker_threads 来实现多线程处理复杂的计算任务,但这些线程依然共享同一进程的资源。当操作系统命令 CPU 去执行一个进程时,实际上是该进程的多个线程之间切换执行,主要是为了充分利用多核 CPU(比如线程数 === CPU 数,线程永不阻塞,没有 io,只存在大量运算)。

    • 示例
    const { Worker, isMainThread, parentPort } = require('worker_threads');
    
    if (isMainThread) {
      const worker = new Worker(__filename);
      
      worker.on('message', (message) => {
        console.log('Received from worker:', message);
      });
    } else {
      let result = 0;
      for (let i = 0; i < 1e9; i++) {
        result += i;
      }
      parentPort.postMessage(result);
    }
    

这个示例展示了如何在 Node.js 中使用 worker_threads 来执行计算密集型任务而不阻塞主线程。

浏览器的多进程架构

“JS 是单线程的” 指的是执⾏ JS 的线程只有⼀个,是浏览器提供的 JS 引擎线程(主线程)。

如今的主流浏览器都是多进程架构的,以 Chrome 为例,它包含了 1 个浏览器主进程、1个 GPU 进程、1 个⽹络进程、多个渲染进程或多个插件进程。默认情况下,Chrome 会为每个 Tab 标签创建⼀个渲染进程。⼀个渲染进程通常由以下线程组成:

  • JS 引擎线程(主线程):
    JavaScript 引擎,也称为 JS 内核,负责处理 JS 脚本,执⾏代码。当主线程空闲且任务队列不为空时,会依次取出任务执⾏。注意,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执⾏ JS 时间过⻓,将导致⻚⾯渲染的阻塞。
  • GUI 渲染线程:
    主要负责⻚⾯的渲染,解析 HTML、CSS,构建DOM树,布局和绘制等。当界⾯需要重绘或者由于某种操作引发重排时,将执⾏该线程。注意:该线程与 JS 引擎线程互斥,当执⾏ JS 引擎线程时,GUI 线程会被挂起,当任务队列空闲时,主线程才会去执⾏ GUI 渲染。
  • 事件触发线程:
    ⽤于控制事件循环,将准备好的事件交给 JS 引擎线程执⾏。当主线程遇到异步任务,如 setTimeOut(或 ajax 请求、⿏标点击事件),会将它们交由对应的线程处理,处理完毕后,事件触发线程会把对应的事件添加到任务队列的尾部,等待 JS 引擎的处理。注意:由于 JS 的单线程关系,队列中的待处理事件都得排队等待,只有在 JS 引擎空闲时才能被执⾏。
  • 定时器触发线程:
    负责执⾏定时器⼀类函数的线程,如 setTimeout,setInterval 等。主线程依次执⾏代码时,遇到定时器,会将定时器交由该线程进⾏计时,当计时结束,事件触发线程会将定时器的回调函数添加到任务队列的尾部,等待 JS 引擎空闲后执⾏。
  • 异步 http 请求线程:
    负责执⾏异步请求⼀类的函数的线程,如 Promise,axios,ajax 等。主线程依次执⾏代码时,遇到异步请求,会将函数交给该线程处理。当监听到状态码变更,如果设置有回调函数,事件触发线程会将相应的回调函数添加到任务队列的尾部,等待 JS 引擎空闲后执⾏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秀秀_heo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值