nodejs中的child_process模块学习

文章介绍了Node.js中的child_process模块,用于在单线程的Node环境中创建子进程来处理CPU密集型任务。讨论了进程、线程的基本概念,以及spawn、exec、execFile和fork等方法的异同和使用场景。示例代码展示了如何使用这些方法执行命令并处理输出。特别提到了fork方法能创建具有IPC通信能力的子进程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在了解child_process之前,我们先来了解几个计算机操作系统中的基本概念,以及他们之间存在的关系。

  1. cup: 计算机包含五大基本硬件运算器、控制器、存储器、输入,输出设备。运算器和控制器集成为中央处理单元即CPU(Central Processing Unit),其主要作用是执行一系列指令运算然后将结果写回。
  2. 进程: 进程是系统进行资源分配和调度的基本单位,同一时间在单个CUP上只能有一个进程运行,它会占用独立的内存。但是我们可能会想到,计算机运行的时候肯定不只是只有一个进程在运行。在现代操作系统中,所有进程会轮流使用cpu,但是由于cpu的运行效率极高,可以在多个任务间快速切换,给我们的感觉就好像多个任务在并发执行。
  3. 线程: 线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,线程共享该进程所拥有的全部资源,但是当有其中一个线程使用某一块共享内存的时候,其他线程必须等待它结束后,才能使用这一块内存。

我们知道node是单线程运行的,当我们用node app.js启动node服务的时候会在服务器上运行一个node的进程,我们的js代码只会在其中的一个线程运行。在node的设计中就是将耗时长的操作代理给操作系统或者其他线程,这部分操作就是磁盘I/O和网络I/O等常见的异步操作,并且将这些耗时的操作从主线程上脱离。虽然node从语言层面不支持创建线程,但是我们可以通过child_process模块创建一个新的进程完成耗时耗费资源的操作,比如说要执行一段上传或下载大文件的shell脚本,然后将执行结果回传给主线程。

本文主要给大家介绍了关于Node.js中child_process模块的相关内容,在介绍child_process模块之前,先来看一个例子。

const http = require('http');
const longComputation = () => {
    let sum = 0;
    for (let i = 0; i < 1e10; i++) {
        sum += i;
    };
    return sum;
};
const server = http.createServer();
server.on('request', (req, res) => {
    if (req.url === '/compute') {
        const sum = longComputation();
        return res.end(`Sum is ${sum}`);
    } else {
        res.end('Ok')
    }
});

server.listen(3000);

可以试一下使用上面的代码启动Node.js服务,然后打开两个浏览器选项卡分别访问/compute和/,可以发现node服务接收到/compute请求时会进行大量的数值计算,导致无法响应其他的请求(/)。
在Java语言中可以通过多线程的方式来解决上述的问题,但是Node.js在代码执行的时候是单线程的,那么Node.js应该如何解决上面的问题呢?其实Node.js可以创建一个子进程执行密集的cpu计算任务(例如上面例子中的longComputation)来解决问题,而child_process模块正是用来创建子进程的。

child_process提供了几种创建子进程的方式

  • 异步方式:spawnexecexecFilefork
  • 同步方式:spawnSyncexecSyncexecFileSync

exec

语法:child_process.exec(command[, options][, callback])

这里的第一个参数 command 就是在 shell 中执行的命令;options 可以设置与执行命令相关的参数,如:cwd(当前工作目录)、shell(执行命令的shell)、uid、gid、encoding等;callback 在命令执行完调用,可通过回调函数的 stdout 获取命令输出。options 和 callback 都是可选参数。比如想在 /Usrs/ben 目录下执行 “ls -l”,那么代码如下:

const { exec } = require('child_process');

exec('ls -l',{cwd: '/Users/liu/Desktop'}, (error, stdout, stderr) => {
  if (error) return;
  console.log('stdout:', stdout);
})

// stdout: total 4792
// -rw-r--r--@ 1 liuchongyang  staff   53248 May 24  2022 10起诉状(一审).doc
// -rw-r--r--@ 1 liuchongyang  staff   34816 May 24  2022 11答辩状(一审).doc
// -rw-r--r--@ 1 liuchongyang  staff   33280 May 24  2022 12质证意见(一审).doc
// -rw-r--r--@ 1 liuchongyang  staff   65024 May 24  2022 13代理词(一审营).doc

execFile

语法:child_process.execFile(file[, args][, options][, callback])

execFile 顾名思义就是执行可执行的文件。通常在 Unix 类型的操作系统中,execFile 相比 exec 执行会更加高效,因为其不产生 shell。在 Windows 系统中,由于 .bat.cmd 文件在没有终端的情况下不能单独执行的,所以不能使用 execFile 来执行,而应该使用 exec 或下面介绍的 spawn 来执行。

execFile 方法中 file 参数是必传,指定要执行的文件;args 可选,是给执行文件传的参数列表;options 和 callback 和 exec中的类似,就不细说了。下面,我们想看一下 node 版本,代码可如下:

const { execFile } = require('child_process');

execFile('/usr/local/bin/node', ['-v'], (error, stdout, stderr) => {
  if (error) return;
  console.log('stdout:', stdout);
})

// stdout: v12.17.0

spawn

语法:child_process.spawn(command[, args][, options])

spawnexec特性类似,都是执行一个命令,但是 spawn 并没有以回调函数的形式来接收 stdout,而是通过子进程对象上 stdout 监听 data 事件来获取标准输出数据。这样的方式使 stdout 以流的形式传输,相比 exec 要等输出结束之后才会调用回调的方式,要高效很多。

我们用 exec 中举的例子,在 /Usrs/ben 目录下执行 “ls -l”,那么代码如下:

const { spawn } = require('child_process');

const subprocess = spawn('ls', ['-l'], {cwd: '/Users/ben'});

subprocess.stdout.on('data', (data) => {
  console.log(data.toString());
});

// total 8
// drwx------@  7 ben  staff   224  7 28 09:36 Applications
// drwx------@ 25 ben  staff   800  7 31 22:19 Desktop
// drwx------@ 24 ben  staff   768  6 21 17:18 Documents
// drwx------@ 79 ben  staff  2528  7 31 20:25 Downloads
// ...

fork

语法:child_process.fork(modulePath[, args][, options])

fork 其实是 spawn 的一个特殊例子,因为 fork() 第一个参数是一个 node module path。args 和 options 参数都和 spawn 一致。但是 fork 执行的是一个 node module,所以 fork 提供了一个特性,即在父子进程之间建立一个 IPC 通道,使父子进程之间通过 send() 方法来互相发送信息。如下例子:

child.js

//child.js
process.on('message',function(msg){
   console.log('child receive msg:', msg) // child receive msg: hello world
   process.send(msg)
})

parent.js

// parent.js
let cp=require('child_process');
let child=cp.fork('./child');
child.on('message',function(msg){
  console.log('parent get messg:',msg); // parent get messg: hello world
});
child.send('hello world');

因为 fork 会在父子进程间建立通信通道,所以如果有同步的 fork,那么这个 IPC 通道就不会存在,所以 fork 没有对应的同步方法。

总结

四种创建子进程的方法中 spawn 和 fork 要相对常用一些,spawn 处理操作系统命令;fork 处理 node module(并且父子进程间会建立 IPC 通道进行通信);exec 是直接使用 shell 执行命令,所以可以方便使用 shell 中的管道等特性,但是输出结果会在回调中一次输出,所以不适合输出数据特别大的情况;execFile 不适合 Windows 系统。

https://u19tul1sz9g.feishu.cn/docx/YTIOdAiBKoWf7yxQBaRcDkCgnsf

<think>好的,我需要帮助用户解决终端创建子进程时出现的“可执行文件格式错误”问题,特别是关于XnView_MP.glibc2.17-x86_64.AppImage文件的错误。首先,我需要了解这个错误的具体原因。常见的可执行文件格式错误可能有几个原因:文件架构不匹配、文件损坏、缺少依赖库或者执行权限问题。 首先,用户提到的错误信息是“failed to execute child process executable format error”,这可能意味着系统无法识别或正确执行该AppImage文件。AppImage文件通常是打包好的应用程序,可以在大多数Linux发行版上运行,但需要满足一些条件。 接下来,我需要参考用户提供的引用内容。引用[2]和[4]提到了卸载MySQL时使用--nodeps选项,这可能涉及到依赖关系的问题。虽然问题不同,但依赖库缺失可能是共同点。引用[3]中的Maven编译错误涉及到配置问题,可能需要调整编译参数,但这里的情况不同。不过,用户的问题可能与系统依赖有关,比如缺少必要的库文件,如glibc版本过低。 首先,我应该检查AppImage文件的权限。如果文件没有执行权限,会导致无法执行。用户需要运行chmod +x命令赋予执行权限。例如:`chmod +x XnView_MP.glibc2.17-x86_64.AppImage`。 然后,考虑架构兼容性。用户系统是x86_64架构,而AppImage也是为此架构编译的,所以应该匹配。但需要确认系统是否支持该架构。可以使用`uname -m`检查,输出应为x86_64。 接下来,检查glibc版本。AppImage可能需要特定版本的glibc,例如2.17或更高。如果用户的系统glibc版本过低,就无法运行。查看glibc版本的方法通常是运行`ldd --version`,这会显示glibc的版本号。如果版本过低,可能需要升级系统或寻找兼容的AppImage版本。 另外,AppImage文件可能损坏,导致无法执行。建议用户验证文件的完整性,比如重新下载并检查哈希值是否匹配官方网站提供的。 如果上述步骤都正常,可能需要使用--appimage-extract参数解压AppImage,然后手动运行其中的可执行文件。例如:`./XnView_MP.glibc2.17-x86_64.AppImage --appimage-extract`,然后进入解压目录运行squashfs-root目录中的AppRun文件。 还有可能需要安装fuse库,因为某些AppImage需要fuse来挂载。安装libfuse2:`sudo apt install libfuse2`(适用于Debian/Ubuntu)。 最后,如果问题依然存在,可能需要查看具体的错误日志,使用strace或直接运行AppImage查看详细输出,例如:`strace ./XnView_MP.glibc2.17-x86_64.AppImage`,或者直接执行并查看错误信息。 总结可能的解决方案步骤:检查权限、验证架构、检查glibc版本、重新下载文件、解压运行、安装依赖库、查看详细日志。需要逐步排除可能的原因。</think>### 分步解决方案 1. **赋予文件执行权限** 使用命令添加可执行权限: ```bash chmod +x XnView_MP.glibc2.17-x86_64.AppImage ``` 若权限不足,可能导致格式错误假象[^2]。 2. **验证系统架构兼容性** 检查系统是否为x86_64架构: ```bash uname -m ``` 若输出非`x86_64`,需下载对应架构的AppImage版本。 3. **检查glibc版本** AppImage通常依赖特定glibc版本,通过以下命令查看当前版本: ```bash ldd --version | head -n1 ``` 若版本低于2.17,需升级系统或寻找更低依赖的AppImage包[^4]。 4. **重新下载并验证文件完整性** 文件损坏会导致格式错误。建议从官网重新下载,并核对SHA256校验值: ```bash sha256sum XnView_MP.glibc2.17-x86_64.AppImage ``` 5. **尝试解压运行** 部分系统需手动解压AppImage: ```bash ./XnView_MP.glibc2.17-x86_64.AppImage --appimage-extract cd squashfs-root ./AppRun ``` 此方法可绕过FUSE依赖。 6. **安装必要依赖库** 安装libfuse2支持FUSE挂载(Debian/Ubuntu): ```bash sudo apt install libfuse2 ``` 7. **调试错误详情** 使用`strace`跟踪系统调用,定位具体错误: ```bash strace -f ./XnView_MP.glibc2.17-x86_64.AppImage ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值