20

I am trying to get my head around creating a non-blocking piece of heavy computation in nodejs. Take this example (stripped out of other stuff):

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

As you can imagine, if I open 2 browser windows at the same time, the first will wait 10 seconds and the other will wait 20, as expected. So, armed with the knowledge that a callback is somehow asynchronous I removed the sleep and put this instead:

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

with the function simply defined:

function doHeavyStuff(callback) {
    sleep(10000);
    callback();
}

that of course does not work... I have also tried to define an EventEmitter and register to it, but the main function of the Emitter has the sleep inside before emitting 'done', for example, so again everything will run block.

I am wondering here how other people wrote non-blocking code... for example the mongojs module, or the child_process.exec are non blocking, which means that somewhere down in the code either they fork a process on another thread and listen to its events. How can I replicate this in a metod that for example has a long process going?

Am I completely misunderstanding the nodejs paradigm? :/

Thanks!

Update: solution (sort of)

Thanks for the answer to Linus, indeed the only way is to spawn a child process, like for example another node script:

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"); });

The calculate.js can take its time to do what it needs and return. In this way, multiple requests will be run in parallel so to speak.

Tallmaris
  • 7,605
  • 3
  • 28
  • 58

5 Answers5

13

You can't do that directly, without using some of the IO modules in node (such as fs or net). If you need to do a long-running computation, I suggest you do that in a child process (e.g. child_process.fork) or with a queue.

Linus Thiel
  • 38,647
  • 9
  • 109
  • 104
  • child_process has no fork() method, just exec and spawn. But you are right, it is the only way to avoid blocking everything... – Tallmaris Mar 03 '12 at 20:53
  • 3
    Here you go: [child_process.fork](http://nodejs.org/docs/latest/api/child_process.html#child_process_child_process_fork_modulepath_args_options). In particular, `fork` allows communication with the child process. – Linus Thiel Mar 04 '12 at 00:01
  • Yup, right... I think I was sitting on an old version of the docs without realizing... :) – Tallmaris Mar 05 '12 at 09:33
6

We (Microsoft) just released napajs that can work with Node.js to enable multithreading JavaScript scenarios in the same process.

your code will then look like:

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"); });

You can read this post for more details.

Daiyi Peng
  • 131
  • 1
  • 4
5

This is a classic misunderstanding of how the event loop is working.

This isn't something that is unique to node - if you have a long running computation in a browser, it will also block. The way to do this is to break the computation up into small chunks that yield execution to the event loop, allowing the JS environment to interleave with other competing calls, but there is only ever one thing happening at one time.

The setImmediate demo may be instructive, which you can find here.

danp
  • 14,876
  • 6
  • 42
  • 48
  • 1
    My problem with this answer is that you are suggesting that computations be run in the event loop. Like blocking IO calls, blocking computation calls should be run _outside_ of the event loop, and use the callback strategy to treat the blocking computation no differently than a blocking IO call. You give it inputs and set it free, and when it's done, it will call you back with its outputs. Now for the browser specifically, where you don't have an underlying thread pool available, a library like setImmediate is crucial, however, doing heavy computation on a browser is the exception not the rule – Michael Plautz Apr 26 '16 at 15:56
  • 1
    Instead of manually handling yielding, spawning a child process allows the OS to handle this for you automatically and probably more efficiently. (If there are additional cores available on the CPU, it will use that too.) This allows your application code to simplify down to just messaging. For browsers, you can use the Web Worker API. – Mizstik Jul 20 '16 at 21:08
3

If you computation can be split into chunks, you could schedule executor to poll for data every N seconds then after M seconds run again. Or spawn dedicated child for that task alone, so that the main thread wouldn't block.

0

Although this is an old post(8 years ago), try to add some new updates to it.

  1. For Nodejs application to get good performance, the first priority is never blocking the event loop. The sleep(10000) method breaks this rule. This is also the reason why Node.js is not suitable for the CPU intensive application. Since the big CPU computation occurs on the event loop thread(it's also the main and single thread of node.js)and will block it.

  2. Multithread programming work_threads was introduced into node.js ecosystem since version 12. Compared with multi-process programming, it's lightweight and has less overhead.

  3. Although multithread was introduced into node.js, but Node.js is still based on the event driven model and async non-block IO. That's node.js's DNA.

Chris Bao
  • 2,418
  • 8
  • 35
  • 62