客户端容器 | 青训营笔记

137 阅读5分钟

进程和线程

进程是 CPU 资源分配的最小单位,线程是 CPU 调度的最小单位。

进程之间相互独立,一个进程的内存空间是共享的,每个线程都可以使用这些共享内存,一个进程可以存在一个或多个线程,也就是单线程或多线程。

多进程形式,允许多个任务同时运行;多线程形式,允许单个任务分成不同的部分运行。

浏览器渲染进程

浏览器是多进程的,其中包括浏览器主进程、第三方插件进程、GPU 进程、浏览器渲染进程。

浏览器渲染进程是浏览器内核,主要负责 HTML、CSS、JS 等文件的解析和执行。

浏览器渲染进程是多线程的,包含的线程主要有 GUI 渲染线程、JS 引擎线程、事件处理线程、定时器触发线程、HTTP 请求线程。

其中,GUI 渲染线程、JS 引擎线程、事件处理线程是常驻线程。

GUI 渲染线程

GUI 渲染线程的执行过程如下:

在这里插入图片描述

  1. 根据 HTML 代码生成 DOM 树,同时浏览器主进程负责下载 CSS 文件
  2. CSS文件下载完成,根据 CSS 代码生成 CSSOM
  3. 将 DOM 树和 CSSOM 整合为渲染树
  4. 计算元素的尺寸、位置等几何信息,布局渲染树(回流)
  5. 绘制页面的像素信息(重绘)
  6. 显示出页面

JS 引擎线程

JS 引擎线程是主线程,一个浏览器渲染进程只有一个 JS 引擎线程在运行,即所谓“JS 是单线程的”。JS 引擎线程负责解析 JS 脚本运行代码,在运行代码时会生成一个 执行栈

事件处理线程

事件处理线程管理着 任务队列,异步任务的回调函数会放在其中,等待 执行栈 中的任务执行完毕后,读取其中任务执行。

定时器触发线程

定时器 setTimeout 、setInterval 通过这个单独的线程计时,计时完毕后添加到 任务队列 中,等待 JS 引擎线程空闲时执行。

HTTP 请求线程

HTTP 请求连接时,会执行这个线程。状态变更时,如果有回调函数,这个回调函数会添加到 任务队列 中,等待 JS 引擎线程执行。

JS 引擎线程与 GUI 渲染线程互斥

一个栗子:
以下代码中,DOM 的操作是同步进行的,而 console.log() 的 “div: ” 部分先打印出来,后面的 div 元素在阻塞后才有值,好像表现为异步。

const body = document.querySelector('body');
const div = document.createElement('div');
console.log('div: ', div);
body.appendChild(div);
for (let i = 0; i < 1000000000; i++);


123456

其实是因为 JS 引擎线程与 GUI 渲染线程互斥,线程之间的“同步”使得 DOM 操作产生了“异步”的效果。

GUI 渲染线程与 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行,GUI 线程执行时同理。

运行机制

运行机制是:宏任务 -> 微任务 -> 调度 GUI 渲染线程 -> 宏任务……

主流浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次。如果一次事件循环的用时超出了16.6ms,那么在这次刷新就没有时间进行样式布局和样式绘制,就会造成页面掉帧卡顿。

React Concurrent 模式背景

React 为了实现“快速响应”,主要在“CPU 瓶颈”和“IO 瓶颈”两个场景下受到制约。其中 CPU 的瓶颈,就是当遇到大计算量的操作或者设备性能不足时,JS 脚本执行时间过长,页面掉帧,造成卡顿。

而 Concurrent 模式就是为了解决 CPU 瓶颈,通过将长任务分拆到每一帧中而不是一直执行,并在每一帧都留出时间渲染 UI,实现时间切片,将同步的更新变为可中断的异步更新。

Electron中渲染进程与渲染进程之间的通信主要有两种方式,一种是通过本地存储来实现,另外一种就是借助主进程进行传递消息。

1. 渲染进程之间通过本地存储进行通信;

渲染进程index传递消息。

// index.js
window.onload = () => {
    var button = document.getElementById("button");
    button.onclick=()=>{
        // 渲染进程index与渲染进程news之间直接进行通信
        localStorage.setItem("msg","世界那么大,我想去看看");
    };
};

渲染进程news获取消息。

// news.js
window.onload=()=>{
    // 渲染进程index与渲染进程news之间直接进行通信
    var msg = localStorage.getItem("msg");
    alert(msg);
    // 世界那么大,我想去看看
}

2. 当前渲染进程向新打开的渲染进程传递消息;

渲染进程index发送消息。

// index.js
const {ipcRenderer} =require("electron");
window.onload = () => {
    var button = document.getElementById("button");
    button.onclick=()=>{
        // 发送消息
        ipcRenderer.send(
            "openNews",
            "钱包那么少,哪儿也去不了"
        );
    };
};

主进程ipcMain接受渲染进程index的消息,并传递给新创建的渲染进程news。

// ipcMain.js
const {BrowserWindow,ipcMain} = require("electron");
const path = require("path");
// 监听渲染进程index传递的消息并创建新的渲染进程news
ipcMain.on("openNews",(e,data)=>{
    
    const newsWindow = new BrowserWindow({
        width:400,
        height:300,
        webPreferences:{
            // 集成node
            nodeIntegration:true,
            // 取消上下文隔离
            contextIsolation:false,
            // 启用remote模块
            enableRemoteModule:true
        }
    });
    newsWindow.loadFile(path.join(__dirname,"../news.html"));
 
    // 监听渲染进程news加载完成
    newsWindow.webContents.on('did-finish-load',(event)=>{
        // 发送消息给渲染进程news
        newsWindow.webContents.send("toNews",data)
    });
});

渲染进程news接受渲染进程index通过主进程ipcMian传递过来的消息。

// new.js
const {ipcRenderer} = require("electron");
// 监听渲染进程index通过主进程ipcMain发送的消息
ipcRenderer.on("toNews",(e,data)=>{
    alert(data);
    // 钱包那么少,哪儿也去不了
});

3. 新打开的渲染进程向之前已经打开的渲染进程传递消息;

渲染进程news发送消息。

// news.js
window.onload = ()=>{
    var button = document.getElementById("button");
    button.onclick = ()=>{
        // 渲染进程news向渲染进程index传递消息
        ipcRenderer.send("sendToIndex","工作没做完,还得继续干")
    };
}

主进程ipcMain接受渲染进程news的消息,并传递给之前已经打开的渲染进程index。

// ipcMain.js
const {BrowserWindow,ipcMain} = require("electron");
const path = require("path");
// 保存已经打开的渲染进程index
var indexId;
 
// 监听渲染进程index传递的消息并创建新的渲染进程news
ipcMain.on("openNews",(e,data)=>{
 
    // 创建渲染进程news之前获取当前渲染进程index的id并保存;
    indexId = BrowserWindow.getFocusedWindow().id;
 
    // 创建渲染进程news
    ......
 
});
 
 
// 监听渲染进程news中传递过来的消息
ipcMain.on("sendToIndex",(e,data)=>{
    // console.log(data);
    // 工作没做完,还得继续干
 
    // 获取之前保存的渲染进程index
    var mainWindow = BrowserWindow.fromId(indexId);
    // 发送消息给渲染进程index
    mainWindow.webContents.send("toIndex",data);
 
})

渲染进程index监听渲染进程news通过主进程ipcMian传递过来的消息。

// index.js
const {ipcRenderer} =require("electron");
 
// 监听渲染进程news通过主进程ipcMain传过来的消息
ipcRenderer.on("toIndex",(e,data)=>{
    console.log(data);
});