child_process 在 nodejs 中产生竞争条件的可能性
child_process spawn Race condition possibility in nodejs
我开始学习和使用 node,我喜欢它,但我不太确定某些功能是如何工作的。也许你可以帮我解决这样一个问题:
我想根据 rest 命令从我的节点服务器生成本地脚本和程序。查看 fs 库,我看到了下面的示例,说明如何生成 child 进程并在其上添加一些 pipes/event 处理程序。
var spawn = require('child_process').spawn,
ps = spawn('ps', ['ax']),
grep = spawn('grep', ['ssh']);
ps.stdout.on('data', function (data) {
grep.stdin.write(data);
});
ps.stderr.on('data', function (data) {
console.log('ps stderr: ' + data);
});
ps.on('close', function (code) {
if (code !== 0) {
console.log('ps process exited with code ' + code);
}
grep.stdin.end();
});
grep.stdout.on('data', function (data) {
console.log('' + data);
});
grep.stderr.on('data', function (data) {
console.log('grep stderr: ' + data);
});
grep.on('close', function (code) {
if (code !== 0) {
console.log('grep process exited with code ' + code);
}
});
让我感到奇怪的是,我不明白我怎么能保证在程序开始之前注册事件处理程序代码运行。这不像有一个 'resume' 函数,你 运行 启动 child。这不是竞争条件吗?当然条件会非常小并且几乎永远不会命中,因为它之后的代码片段如此短,但如果是这样,我宁愿不以这种方式编写代码,这是出于良好的习惯。
所以:
1)如果不是竞争条件,为什么?
2) 如果它是一个竞争条件,我怎么能以正确的方式写它?
感谢您的宝贵时间!
这不是竞争条件。 Node.js 是单线程的,按照先到先得的原则处理事件。新事件放在事件循环的末尾。 Node 将以同步方式执行您的代码,其中一部分将涉及设置事件发射器。当这些事件发射器发出事件时,它们将被放在队列的末尾,并且在 Node 完成执行其当前正在处理的任何代码之前不会被处理,这恰好与注册侦听器的代码相同。因此,监听器总是会在事件被处理之前注册。
鉴于已接受答案的评论中存在轻微的冲突和歧义,下面的示例和输出告诉我两件事:
- 子进程(指的是
spawn
返回的节点对象)不发出任何事件,即使真正的底层进程处于活动/执行状态。
- IPC 的管道在执行子进程之前设置。
两者都很明显。冲突是w.r.t。 OP问题的解释:-
实际上'yes',如果需要考虑真正的子进程的副作用,这就是数据竞争条件的缩影。但是 'no',就 IPC 管道管道而言,不存在数据竞争。数据被写入缓冲区并作为(更大的)blob 检索(如前所述)上下文完成允许事件循环继续。
下面看到的第一个数据事件不是将 1 个而是 5 个块推送到我们阻塞时由子进程写入 stdout.. 因此没有任何丢失。
示例:
let t = () => (new Date()).toTimeString().split(' ')[0]
let p = new Promise(function (resolve, reject) {
console.log(`[${t()}|info] spawning`);
let cp = spawn('bash', ['-c', 'for x in `seq 1 1 10`; do printf "$x\n"; sleep 1; done']);
let resolved = false;
if (cp === undefined)
reject();
cp.on('error', (err) => {
console.log(`error: ${err}`);
reject(err);
});
cp.stdout.on('data', (data) => {
if (!resolved) {
console.log(`[${t()}|info] spawn succeeded`);
resolved = true;
resolve();
}
process.stdout.write(`[${t()}|data] ${data}`);
});
let ts = parseInt(Date.now() / 1000);
while (parseInt(Date.now() / 1000) - ts < 5) {
// waste some cycles in the current context
ts--; ts++;
}
console.log(`[${t()}|info] synchronous time wasted`);
});
Promise.resolve(p);
输出:
[18:54:18|info] spawning
[18:54:23|info] synchronous time wasted
[18:54:23|info] spawn succeeded
[18:54:23|data] 1
2
3
4
5
[18:54:23|data] 6
[18:54:24|data] 7
[18:54:25|data] 8
[18:54:26|data] 9
[18:54:27|data] 10
我开始学习和使用 node,我喜欢它,但我不太确定某些功能是如何工作的。也许你可以帮我解决这样一个问题:
我想根据 rest 命令从我的节点服务器生成本地脚本和程序。查看 fs 库,我看到了下面的示例,说明如何生成 child 进程并在其上添加一些 pipes/event 处理程序。
var spawn = require('child_process').spawn,
ps = spawn('ps', ['ax']),
grep = spawn('grep', ['ssh']);
ps.stdout.on('data', function (data) {
grep.stdin.write(data);
});
ps.stderr.on('data', function (data) {
console.log('ps stderr: ' + data);
});
ps.on('close', function (code) {
if (code !== 0) {
console.log('ps process exited with code ' + code);
}
grep.stdin.end();
});
grep.stdout.on('data', function (data) {
console.log('' + data);
});
grep.stderr.on('data', function (data) {
console.log('grep stderr: ' + data);
});
grep.on('close', function (code) {
if (code !== 0) {
console.log('grep process exited with code ' + code);
}
});
让我感到奇怪的是,我不明白我怎么能保证在程序开始之前注册事件处理程序代码运行。这不像有一个 'resume' 函数,你 运行 启动 child。这不是竞争条件吗?当然条件会非常小并且几乎永远不会命中,因为它之后的代码片段如此短,但如果是这样,我宁愿不以这种方式编写代码,这是出于良好的习惯。
所以: 1)如果不是竞争条件,为什么? 2) 如果它是一个竞争条件,我怎么能以正确的方式写它?
感谢您的宝贵时间!
这不是竞争条件。 Node.js 是单线程的,按照先到先得的原则处理事件。新事件放在事件循环的末尾。 Node 将以同步方式执行您的代码,其中一部分将涉及设置事件发射器。当这些事件发射器发出事件时,它们将被放在队列的末尾,并且在 Node 完成执行其当前正在处理的任何代码之前不会被处理,这恰好与注册侦听器的代码相同。因此,监听器总是会在事件被处理之前注册。
鉴于已接受答案的评论中存在轻微的冲突和歧义,下面的示例和输出告诉我两件事:
- 子进程(指的是
spawn
返回的节点对象)不发出任何事件,即使真正的底层进程处于活动/执行状态。 - IPC 的管道在执行子进程之前设置。
两者都很明显。冲突是w.r.t。 OP问题的解释:-
实际上'yes',如果需要考虑真正的子进程的副作用,这就是数据竞争条件的缩影。但是 'no',就 IPC 管道管道而言,不存在数据竞争。数据被写入缓冲区并作为(更大的)blob 检索(如前所述)上下文完成允许事件循环继续。
下面看到的第一个数据事件不是将 1 个而是 5 个块推送到我们阻塞时由子进程写入 stdout.. 因此没有任何丢失。
示例:
let t = () => (new Date()).toTimeString().split(' ')[0]
let p = new Promise(function (resolve, reject) {
console.log(`[${t()}|info] spawning`);
let cp = spawn('bash', ['-c', 'for x in `seq 1 1 10`; do printf "$x\n"; sleep 1; done']);
let resolved = false;
if (cp === undefined)
reject();
cp.on('error', (err) => {
console.log(`error: ${err}`);
reject(err);
});
cp.stdout.on('data', (data) => {
if (!resolved) {
console.log(`[${t()}|info] spawn succeeded`);
resolved = true;
resolve();
}
process.stdout.write(`[${t()}|data] ${data}`);
});
let ts = parseInt(Date.now() / 1000);
while (parseInt(Date.now() / 1000) - ts < 5) {
// waste some cycles in the current context
ts--; ts++;
}
console.log(`[${t()}|info] synchronous time wasted`);
});
Promise.resolve(p);
输出:
[18:54:18|info] spawning
[18:54:23|info] synchronous time wasted
[18:54:23|info] spawn succeeded
[18:54:23|data] 1
2
3
4
5
[18:54:23|data] 6
[18:54:24|data] 7
[18:54:25|data] 8
[18:54:26|data] 9
[18:54:27|data] 10