关于node.js:node js如何创建非阻塞计算

NodeJs how to create a non-blocking computation

我正在尝试在nodejs中创建一个非阻塞的繁重计算。举个例子(去掉其他东西):

1
2
3
4
5
http.createServer(function(req, res) {
    console.log(req.url);
    sleep(10000);
    res.end('Hello World');
}).listen(8080, function() { console.log("ready"); });

可以想象,如果我同时打开两个浏览器窗口,第一个窗口将等待10秒,另一个窗口将等待20秒,如预期的那样。因此,在知道回调是异步的情况下,我删除了睡眠,并将其改为:

1
2
3
doHeavyStuff(function() {
    res.end('Hello World');
});

使用简单定义的函数:

1
2
3
4
function doHeavyStuff(callback) {
    sleep(10000);
    callback();
}

那当然不行…我还试图定义一个eventEmitter并注册到它,但是Emitter的主要功能在发出"done"之前就已经有了睡眠,所以一切都将再次运行block。

我想知道其他人是怎么写非阻塞代码的…例如,mongojs模块或子进程exec是非阻塞的,这意味着在代码的某个地方,它们要么在另一个线程上分叉一个进程,然后监听它的事件。我如何在一个有着漫长过程的metod中复制它?

我是否完全误解了Nodejs的模式?:

谢谢!

更新:解决方案(排序)

感谢Linus的回答,实际上唯一的方法是生成子进程,例如另一个节点脚本:

1
2
3
4
5
6
7
8
9
http.createServer(function(req, res) {
    console.log(req.url);

    var child = exec('node calculate.js', function (err, strout, strerr) {
        console.log("fatto");
        res.end(strout);
    });

}).listen(8080, function() { console.log("ready"); });

calculate.js可以花时间完成它需要的并返回。这样,就可以说,多个请求将并行运行。


如果不使用节点中的某些IO模块(如fsnet),则无法直接执行此操作。如果您需要进行长时间运行的计算,我建议您在子进程(如child_process.fork或队列)中进行计算。


我们(微软)刚刚发布了napajs,它可以与node.js一起工作,在同一进程中实现多线程的javascript场景。

然后,您的代码将如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var napa = require('napajs');

// One-time setup.
// You can change number of workers per your requirement.
var zone = napa.zone.create('request-worker-pool', { workers: 4 });

http.createServer(function(req, res) {
    console.log(req.url);

    zone.execute((request) => {
        var result = null;
        // Do heavy computation to get result from request
        // ...
        return result;
    }, [req]).then((result) => {
        res.end(result.value);
    }
}).listen(8080, function() { console.log("ready"); });

你可以阅读这篇文章了解更多细节。


这是对事件循环如何工作的一个典型误解。

这不是节点独有的功能——如果在浏览器中有一个长时间运行的计算,它也会阻塞。实现这一点的方法是将计算拆分为小块,从而为事件循环生成执行,允许JS环境与其他竞争调用交错,但一次只发生一件事。

setImmediate演示可能很有启发性,您可以在这里找到。


如果您的计算可以分割成块,您可以安排执行器每N秒轮询一次数据,然后在M秒后再次运行。或者单独为该任务生成专用的子线程,这样主线程就不会阻塞。