Electron 保活方案:用子进程彻底解决原生插件崩溃问题

项目引入 Node 原生插件(.node) 后,出现高崩溃率主进程阻塞问题。

通过 child_process + fork 子进程 实现插件保活,保证系统稳定运行。

【process】

  • 全局对象,直接用,不用require
  • 代表当前运行的Node.js进程
  • 一个应用启动后只有一个主进程
  • 作用:获取进程信息、环境变量、程序退出、系统信号监听等

【child process】

  • Node.js内置模块,必须require('child_process')
  • 作用:在主进程中创建独立子进程
  • 子进程可以执行:其他Node脚本、JS/TS、shell命令、Native插件
  • 目的:解决单线程阻塞、利用多核CPU、崩溃隔离,保证主进程稳定

【fork子进程机制】(保活核心)

  1. fork作用:

    • 创建全新独立的Node.js进程
    • 运行指定的JS/TS文件
    • 父子进程完全隔离、不共享内存
  2. process指向规则(关键)

    • fork外部文件:process = 主进程
    • fork内部文件:process = 当前子进程

【electron为什么要把Native插件放进fork子进程】

  • 避免阻塞主进程 / UI线程
  • 插件崩溃不会导致整个应用崩溃,可自动重启
  • 主进程职责更清晰:只负责生命周期管理+IPC转发
  • 实现插件保活,提升应用稳定性

【核心总结】

  • Nodejs是单线程,主进程若做大量计算或插件逻辑会直接阻塞,卡死甚至崩溃

而child_process可以:

  • 开启新进程,分担密集型任务
  • 子进程崩溃不影响主进程
  • 充分利用多核CPU
  • 实现崩溃自动重启(保活机制)

【实践】

插件相关API必须放在fork子进程中,主进程只负责

  • 启动子进程
  • 管理子进程生命周期
  • 将子进程事件转发给渲染进程

【好处】

  • 避免native模块阻塞主进程的UI线程
  • native模块崩溃不会导致整个Electron应用崩溃(子进程可以重启)
  • 主进程代码更清晰,只处理 IPC 转发

fork的作用:启动一个全新的process(新进程),运行指定文件,父子是两个完全独立的进程

结构图:

代码示例:

keepAlive:【主进程fork + 保活】

复制代码
import { fork } from 'child_process';
const isDev = process.env.NODE_ENV === 'development';
let worker;
function startWorker() {

    const workerPath = isDev
    ?
    path.join(__dirname, 'nodeplugin.ts')
    :
    path.join(__dirname, 'nodeplugin.js');
    
    // 启动子进程
    worker = fork(
        workerPath,
        [],
        {
            execArgv: isDev
                ?
                ['-r', 'ts-node/register/transpile-only']
                :
                [],
            env: {
                ...process.env,
                ELECTRON_IS_PACKAGED: isDev ? 'true' : 'false'
            }
        }
    );
    
    worker.on('spawn', () => {
        // 触发通信:向子进程发送通信
        worker!.send({type: '', data: null})
    })
    
    worker.on('message', (msg) => {
        console.log('插件返回:', msg);
    });
    
    worker.on('exit', (code, signal) => {
        // 非正常退出
        if (code !== 0) {
            console.warn(`[蓝牙进程] 异常退出 code=${code},准备重启`); 
            restartWorker();
        } else {
            console.log(`[蓝牙进程] 正常退出`);
        }
    });
}
    
function restartWorker() {
    if(!worker) return;
    
    // 清理旧 worker
    worker.removeAllListeners();
    worker.kill();
    worker = null;
    
    // 重新启动
    startWorker()
}

pluginNode.ts【子进程】

复制代码
const native = require('./xxx.node');

// 接收主进程消息
process.on('message', (msg) => {

    console.log(msg)
    
    // 处理逻辑
    switch(msg.type) {
        case 'case1':
            ...
            break;
        .
        .
        .
    }
    
    // 触发通信:向外层woker发送数据,实现回传
    process.send(result);
});

【父子进程通信坑点:Uint8Array类型丢失】

  • 在跨进程传输后Uint8Array变成了普通对象,导致无法使用byteLength等属性

【现象】:

  • 子进程里:Uint8Array(39) [...] (类型化数组)
  • 父进程收到:{ '0': 0, '1':0 ... } (普通对象)
  • 未做任何转换 → 但类型自动改变。

【根本原因】:

  • 进程之间不能直接共享内存

  • 跨进程 IPC 必须经过序列化,才能跨进程传递

  • Node 对 TypedArray / Uint8Array 的序列化规则:自动转为「索引 - 值」普通对象

    复制代码
      子进程:Uint8Array
            ↓
      IPC 序列化(自动)
            ↓
      父进程:普通对象 { 0:xx, 1:xx ... }

【解决方案】

  • 传输时用: const value = Array.from(payload)
  • 接收时用: const payloadArray = Uint8Array.from(payload);

【最终总结】

  1. 主进程 = process,全局唯一,严禁阻塞

  2. child_process.fork() = 创建独立子进程

  3. Native插件必须放在子进程,防止崩溃拖垮整个应用

  4. 子进程崩溃 → 主进程自动重启 → 实现保活

  5. Uint8Array跨进程会变对象 → 使用Array.form / new Uint8Array还原

相关推荐
zhensherlock1 小时前
Protocol Launcher 系列:Working Copy 文件操作与高级命令详解
javascript·git·typescript·node.js·自动化·github·js
神の愛7 小时前
左连接查询数据 left join
java·服务器·前端
小码哥_常8 小时前
解锁Android嵌入式照片选择器,让你的App体验丝滑起飞
前端
郑寿昌9 小时前
IIoT本体迁移的领域扩展机制
服务器·前端·microsoft
深海鱼在掘金10 小时前
Next.js从入门到实战保姆级教程(第十一章):错误处理与加载状态
前端·typescript·next.js
深海鱼在掘金10 小时前
Next.js从入门到实战保姆级教程(第十二章):认证鉴权与中间件
前端·typescript·next.js
energy_DT10 小时前
2026年十五五油气田智能增产装备数字孪生,CIMPro孪大师赋能“流动增产工厂”三维可视化管控
前端
龙猫里的小梅啊10 小时前
CSS(四)CSS文本属性
前端·css
MXN_小南学前端10 小时前
watch详解:与computed 对比以及 Vue2 / Vue3 区别
前端·javascript·vue.js
饭小猿人10 小时前
Flutter实现底部动画弹窗有两种方式
开发语言·前端·flutter