码字不易,期待您的点赞
目录
第一步 尝试将所有第一步先把fsPromise 异步操作都换成了fs.writeFileSync类似的同步操作
项目背景
最近接手的一个新需求,需要将一款windows版本的electron桌面程序切换到信创环境下使用。项目中主要使用了electron中的BrowserWindow以及webview的功能。在webview中嵌入了全部业务逻辑的H5界面,界面使用vue3, 后端使用nodejs+express开发的。 全部功能是其它同事编写的,并且在windows环境下运行完全没有问题。
因为了解到nodejs、以及electron中使用的chromium 都是支持跨平台的,并且兼容性都比较好。所以在开始刚处理这个需求的时候,认为简单熟悉一下项目代码,重新编译一下就能在linux环境下很好的运行起来。 但没有预测到的是,业务逻辑中有一个功能需要写入文件。每次执行到这块的逻辑就会导致整个程序阻塞、完全没有响应。同时系统CPU飙升,虚拟机是2核CPU。监控软件显示,其中electron进程能占用了120%左右。
阅读说明
本篇文章重在记录问题的解决过程,尝试了很多方案。最终问题导致的原因仅供参考,不一定适用读者的情况。但是会给读者提供一些思路。 问题解决过程借助了AI(DeepSeek),确实能给解决提供很多思路。如果想直接看结论,可以滑到最后。 这里展示的项目架构仅为一个简易版本,为了说明这个问题的解决过程,读者无需纠结架构设计的合理性。
前情概要
简易架构图

启动Electron进程之后,分为主进程和渲染进程 两个独立进程。 主进程中会先通过child_process.fork("./server/index.js") 启动web服务器,随后通过BrowserWindow.loadURL方法加载web页面。
//主进程的main.js
function createWindow(){
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
devTools: true,
sandbox:false,
nodeIntegration: true, // 允许使用 Node.js API
contextIsolation: false, // 禁用上下文隔离
webSecurity: false, // 禁用 web 安全策略
allowRunningInsecureContent:true,
webviewTag: true, // 允许使用 webview 标签
}
})
win.loadURL('http://localhost:13001');
}
app.whenReady().then(() => {
let electron_server_process=child_process.fork(path.join(__dirname,'view-server','index.js'));
createWindow();
})
渲染进程会将preload.js加载到web页面中,在preload.js中会同步加载lib.js
preload和lib的代码比较多,这里不列出来。后续说明问题时,逐一列出。
问题描述
项目中有一个功能,在electron的web页面中,打开A目录的F文件,修改后再将文件内容更新到A目录F文件中。 F文件的读取和更新都是在lib.js中实现的,通过fs.writeFileSync 。
问题:每次保存文件时,都出现了程序卡死,CPU飙升的情况。经过初步排查,就是在执行fs.wirteFileSync之后,如果把这个注释掉,程序不会出现问题。

解决过程
思考1 事件循环冲突
因为在windows环境是正常的,所以怀疑是electron、chromium或node程序在linux环境有不一样的表现导致的。 AI分析给出了几种可能得原因,这里我比较倾向于是windows和linux环境的事件循环机制是不同的。于是按照这个思路修改代码解决。
通过阅读代码,分析preload和lib程序中使用过fsPromise ,await、async 、fs.同步函数、http请求等与事件循环相关的代码。 因此怀疑是否是在linux环境下fsPromise或者其它异步函数的事件循环与web页面中的事件循环有冲突。
第一步 尝试将所有第一步先把fsPromise 异步操作都换成了fs.writeFileSync类似的同步操作
//渲染进程preload.js
//修改前
await fsPromise.writeFile(filename,content,"utf8");
//修改后
fs.writeFileSync(filename,content,"utf8");
测试程序,从表现上看稍有缓解但未解决。
第二步 尝试将所有文件处理放在主进程
第一步修改的内容都是在渲染进程中处理的,所以尝试将这些处理都放在主进程中。通过阅读Electron官方API文档,实现ipcRender.sendSync和ipcMain.on方法,实现渲染进程通知主进程调用fs.writeFileSync
// 主进程的main.js中 (whenReady事件函数内)
ipcMain.handle("writeFile",function(event,file,content){
fs.writeFileSync(file,content);
//event.returnValue=true;
return true;
})
//渲染进程的preload.js中 将所有的fs.writeFileSync改为如下
ipcRenderer.sendSync("writeFile",filepath,content);
问题仍未解决
第三步 通过work_thread实现
分析第二步未解决的原因可能是sendSync这个函数可能会影响事件循环和回调,因此又通过AI获取到另外一个work_thread的方法,重新写代码验证,仍没有解决。还是会卡住。
// preload.js中新增函数
const { Worker,workerData } = require("node:worker_threads");
function thread_write_file(filepath, content,_callback){
console.log("worker thread begin");
const worker = new Worker(path.join(__dirname,"thread_1.js"),{
workerData:{
"file":filepath,
"content":content
}
});
worker.on("message",function(data){
console.log("on message:",data);
if(data && data.result) {
_callback && _callback(true)
} else {
_callback && _callback(false)
}
})
worker.on("close",function(){
console.log("thread close");
})
}
//新增thread_1.js
const { Worker,parentPort,workerData } = require("node:worker_threads");
const fs = require("node:fs");
console.log("workerData:",workerData);
try {
fs.writeFileSync(workerData.file,workerData.content);
parentPort.postMessage({
result:true
});
console.log("writeFileSuccess")
} catch(e) {
parentPort.postMessage({
result:false,
error: e.message,
file: workerData.file
});
console.log("writeFile Failed:",e.message);
}
这里尝试了两个版本的work_thread,如上是使用nodejs的word_thread,但Electron会报错,不允许使用这个库。
下边使用了web api中的work线程,在MDN的Reference在线文档中可以找到使用说明。
//preload.js
function thread_write_file(filepath, content,_callback){
console.log("worker thread begin");
const worker = new Worker("thread.js");
worker.addEventListener("message",function(event){
console.log("on message:",event.data);
if(event.data && event.data.result) {
_callback && _callback(true)
} else {
_callback && _callback(false)
}
})
worker.postMessage({
"file":filepath,
"content":content
});
}
//thread.js
self.onmessage=function(workerData){
try {
console.log("workerData:",workerData);
fs.writeFileSync(workerData.file,workerData.content);
self.postMessage({
result:true
});
console.log("writeFileSuccess")
} catch(e) {
self.postMessage({
result:false,
error: e.message,
file: workerData.file
});
console.log("writeFile Failed:",e.message);
}
}
按照如上方法,将所有写文件的处理都通过执行thread_write_file代替,发现问题仍存在。
思考2 权限问题
本小结无需提供代码
- AI交流,分析可能与文件的读写权限有关系,所以又顺着这个方向查询验证,发现确实这个目录和文件是 drwxrwxr-x 权限,感觉基本上定位到了。但也有疑问,命名目录和文件都属于当前用户,为什么自己用户创建的文件,有w权限还不能写。难道是执行程序时用的other用户?
- 尝试使用chmod +777 增加权限, 验证了一次,发现好用了一下。但是过了一会又不好用了。继续分析会不会是目录和文件所属组、所有者和执行程序不一致导致的。发现没有这样的情况
- 尝试sudo执行electron,但electron启动之后一直白屏,且有终端报错信息,AI查询答复是sudo权限不能访问x- 桌面系统,之后放弃了这个方向
- 尝试将fs.WriteFileSync的操作重新放到主进程的child.fork子进程中实现,也就是在网页的13001服务端实现。不放在electron的进程了。重新写代码,验证问题仍未解决。
- 因为发现fs.ReadFileSync没有问题,只有write的是有问题,分析与文件写入操作会不会与其他地方有冲突。但不能确认具体位置 。
注意:这里发现:每次执行fs.writeFileSync之后,发现文件都是已经成功写入的。fs.writeFileSync后边的代码不能执行了。
思考3 第三方组件冲突
经过了思考2的尝试,突然想到lib.js代码中有一段内容通过第三方组件chokidar实时监控文件变化,并重新读取处理。因此怀疑是这里冲突,将这段代码注释掉之后,发现确实没有问题了
//lib.js中的实施监控文件变更的关键代码
var chokidar = require('chokidar');
var watcher = chokidar.watch(actionsfilepath);
watcher.on('change', function(path) {
console.log('File', path, 'has been changed');
atomLogWrite.info("watcher.on:",'File['+ path+']has been changed');
var tmp = path.split('\\');
var i = 0;
while(tmp[i] != "tmpfile") {
i=i+1;
}
if((tmp.length-i)>4){
if((tmp.length - i) == 6){
var username = tmp[i+1];
var proj = tmp[i+2];
var branch = tmp[i+3];
var groupname = tmp[i+4];
tmp = tmp[i+5];
tmp = tmp.split('\.');
var ctrlname = tmp[0];
}else if((tmp.length - i) == 5){
var username = tmp[i+1];
var proj = tmp[i+2];
var branch = tmp[i+3];
var groupname = "";
tmp = tmp[i+4];
tmp = tmp.split('\.');
var ctrlname = tmp[0];
}
var scriptcontent = fs.readFileSync(path,'utf8');
httpPost('/develop/state/savescript', { 'username': username, 'proj': proj, 'branch': branch, 'stateGroupName': groupname, 'name': ctrlname, 'script': scriptcontent }, (response) => {
if (response.result == true) {
console.log('save to server success');
atomLogWrite.info("watcher.on:savescript:: save to server success");
} else {
console.log('save to server failed');
atomLogWrite.info("watcher.on:savescript:: save to server failed");
}
});
}
}
});
此时,分析应该是chokidar组件与fs.writeFileSync在linux环境中有冲突导致的,因此做了两种情况的尝试。
第一分析是否是在watch的时候缺少某些参数,因此查询npm官网的chokidar包说明。分析可能与如下两个参数有关系

所以尝试将其都配置为true , 验证发现问题仍未解决。
//lib.js中变更的代码
var watcher = chokidar.watch(actionsfilepath ,{
usePolling : true,
interval : 200 , //这里配200仅为尝试
awaitWriteFinish :true
});
检查了一下chokidar当前使用的版本和最新版本 ,发现都是4.0.3,应该使用的就是最新版本,无法通过升级解决。通过npm view chokidar version 查看npm服务器中的最新版本。
# 通过npm view chokidar version 查看npm服务器中的最新版本
# 将version 改为 versions 可以列举所有版本
star@star-vmwarevirtualplatform:~/code/app$ npm view chokidar version
4.0.3
通过npm list chokidar version 查看本机安装的版本
star@star-vmwarevirtualplatform:~/code/app$ npm list chokidar version
electron-zjudp@1.0.0 /home/star/code/app
└── chokidar@4.0.3
发现版本是最新的,不能通过升级解决。 所以做了第二次尝试去掉实时监控功能,由回调函数实现。
此时的程序执行顺序是这样的:
- web页面打开文件
- lib.js中读取文件提供给web
- web中修改文件 并发送写入请求 给 web的server端
- sever端写入文件 结果响应给 web页面
- web页面调用lib.js中的window.electron.onSave方法
- onSave方法,执行原来chokidar监控的change事件里的方法
如下提供了onsave方法的处理代码
//lib.js中去掉chokidar监控,将change事件的处理改为在onSave接口中调用
function fileChanged(filepath) {
setTimeout(()=>{
console.log('lib::File', filepath, 'has been changed');
atomLogWrite.info("watcher.on:",'File['+ filepath+']has been changed');
var tmp = filepath.split("\\")//path.sep
var i = 0;
while(tmp[i] != "tmpfile") {
i=i+1;
}
if((tmp.length-i)>4){
if((tmp.length - i) == 6){
var username = tmp[i+1];
var proj = tmp[i+2];
var branch = tmp[i+3];
var groupname = tmp[i+4];
tmp = tmp[i+5];
tmp = tmp.split('\.');
var ctrlname = tmp[0];
}else if((tmp.length - i) == 5){
var username = tmp[i+1];
var proj = tmp[i+2];
var branch = tmp[i+3];
var groupname = "";
tmp = tmp[i+4];
tmp = tmp.split('\.');
var ctrlname = tmp[0];
}
var scriptcontent = fs.readFileSync(filepath,'utf8');
if(scriptcontent !=''){
atomLogWrite.info("watcher.on:username = [%s],proj = [%s],branch = [%s],ctrlname = [%s],groupname[%s],path[%s]", username, proj, branch, ctrlname, groupname, filepath);
httpPost('/develop/state/savescript', { 'username': username, 'proj': proj, 'branch': branch, 'stateGroupName': groupname, 'name': ctrlname, 'script': scriptcontent }, (response) => {
if (response.result == true) {
console.log('save to server success');
atomLogWrite.info("watcher.on:savescript:: save to server success");
} else {
console.log('save to server failed');
atomLogWrite.info("watcher.on:savescript:: save to server failed");
}
});
}
}
}, 500);
}
//window.electron={}是在preload.js中实现的
if(window.electron ){
window.electron.onSave=fileChanged;
}
这样操作之后仍然没有解决,还会卡住。 此时,再分析,可以排除是chokidar的原因。
最终解决
经过多次尝试,都没有解决。现在发现能尝试的都已经试过了,只有httpPost异步请求这个方法没有验证过,分析可能还是与这里的异步处理有关系,可能会影响事件回调。
//lib.js
function getCookie(name) {
// 1. 将 Cookie 字符串拆分为键值对数组
const cookies = persist_cookie.split(';').map(cookie => cookie.trim());
// 2. 查找目标 Cookie
const targetCookie = cookies.find(cookie => cookie.startsWith(`${name}=`));
// 3. 返回 Cookie 值(如果存在)
return targetCookie ? targetCookie.split('=')[1] : null;
}
function httpPost(uri,data,cb){
let postdata = JSON.stringify(data);
http.request({
hostname: '127.0.0.1',
port: 12001,
path: uri,
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Content-Length': Buffer.byteLength(postdata),
'accept': 'application/json, text/plain, */*',
'Cookie': 'myapp='+getCookie('myapp'),
'connection': 'keep-alive',
'sec-fetch-dest':'empty',
'sec-fetch-mode':'cors',
'sec-fetch-site':'cross-site',
'sec-fetch-storage-access':'active',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) electron-zjudp/1.0.0 Chrome/136.0.7103.168 Electron/36.5.0 Safari/537.36'
}
},(res) => {
console.log(`STATUS: ${res.statusCode}`);
//res.setEncoding('utf8');
let responseData = '';
res.on('data', (chunk) => {
//console.log(`BODY: ${chunk}`);
responseData+= chunk;
});
res.on('end', () => {
cb(JSON.parse(responseData));
});
res.on('error', (err) => {
console.error('Error:', err);
cb({result:false, message: '请求失败'});
})
}
).end(postdata);
}
因此决定在,fileChanged方法中,将httpPost的内容去掉。 但是在修改代码过程中无意间关注到了这段代码:
//lib.js的fileChanged函数中
var tmp = filepath.split("\\")
var i = 0;
while(tmp[i] != "tmpfile") {
i=i+1;
}
恍然大悟!!!!
这段代码,之前的开发人员这样写目的是想要通过循环的方式找到路径中tmpfile命名的目录。问题的原因就在这里, 因为现在是在linux环境下运行,所以filepath.split("\\")时,不应该使用\分割,在linux环境中应该是/ , 所以导致tmp数组本身只有一个元素,理论上while循环tmp[i] 永远不会是“tmpfile” , 这就产生的while死循环,cpu一直飙升。
通过修改为如下代码验证, 完全解决了卡住问题,CPU也不会再飙升了,非常稳定:
//lib.js的fileChanged函数中
//将分隔符改为path.sep,可以兼容不同的操作系统
var tmp = filepath.split(path.sep)
var i = 0;
while(tmp[i] != "tmpfile") {
i=i+1;
}
结语
这个问题花费了1周的时间,每天白天有其它任务,晚上下班和周末分析、调试。终于找到了问题的直接原因。中途也考虑过不再分析,更换为其它的文件写入方案,但是处于找不到问题原因不甘心的心态,还是坚持到最后了。这一点应该非常应该表扬一下自己。
查询这个问题耗时比较长的其中一个原因,和对代码的不熟悉的关系也非常大,接手别人的代码,粗略的读了一下整体执行逻辑就开始进程需求改造和调试了。 相信如果是代码的最初作者分析这个问题可能很快就能定位到。
通过这次的代码分析,尝试和接触了很多东西,作为程序员真的是要多多的实战中、解决问题,这个过程比较艰难,但会学到知识、提高解决问题的能力和思路。
这次解决问题的收获:
- - 了解swap分区和功能
- - 学习、编写到了node中的work_thread和浏览器中的Worker,两种工作线程
- - 学习编写了Electron的 ipc通讯,使用了之前没有使用的方法。
- - 把Electron的官网翻了一遍
通过这个问题,还可以总结出来。可能有一些问题并不需要上升到太深层次的原理去分析,或者 不需要首先就去考虑是不是第三方程序的问题。 首先还是要把项目的源码内容读熟,理解,先从项目源码找问题。然后再排查三方组件、开源库等程序的问题,寻找解决方案。
到这里,我的分享也结束了。
❤感谢阅读❤

被折叠的 条评论
为什么被折叠?



