1

I was reading http://howtonode.org/understanding-process-next-tick However, the code it come with does not implement CPU-intensive task.

I tried to write my version. But it is wrong.

None of the IO is serving after compute() get executed.

So, my question is: What's the proper way to use nextTick() function under this scenario ?

I don't want to block IO when compute() is executing.

var http = require('http');

function compute() {

    // performs complicated calculations continuously
    // ...
    var result = 0;
    for(var i = 0; i < 1000000; i++){
        for(var j = i;  j < 1000000; j++){
            result += i + j;
        }
    }
    process.nextTick(compute);
}

http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World');
}).listen(5000, '127.0.0.1');


compute();
CodeFarmer
  • 2,644
  • 1
  • 23
  • 32

4 Answers4

2

nextTick simply schedules your function to be invoked on the next tick of the event loop. It does not give that function magical non-blocking properties; JavaScript is still single-threaded. If the function blocks (by performing a lot of CPU-bound work), it will still cause I/O events to queue until the function finishes.

If you need to do CPU-intensive work, do it in a worker process.

Community
  • 1
  • 1
josh3736
  • 139,160
  • 33
  • 216
  • 263
1

nextTick is a blocking call (in node v0.10.29 at least, where I tested this), it prevents other events from running. use setImmediate()

Andras
  • 2,995
  • 11
  • 17
  • 2
    `nextTick` does not block. – josh3736 Dec 03 '14 at 03:29
  • functions queued with nextTick bypass the event queue and are run immediately after the currently running continable exits. So if they self-recurse like above, then they block the http server from processing requests. – Andras Dec 03 '14 at 03:34
  • 2
    That's a different story. `nextTick` itself does not block. It schedules something to run "later" and control passes back to your code immediately. If a function is CPU-intensive like OP's example, it doesn't matter when precisely the call is scheduled; it's still going to prevent I/O events from firing while it runs. – josh3736 Dec 03 '14 at 03:37
  • in nodejs a "blocking call" prevents others from getting run. Using nextTick makes the current thread run longer, blocking other threads. Not so different. – Andras Dec 03 '14 at 03:44
  • 1
    Incorrect. There is a subtle but important distinction between a blocking call and a function that starves the event loop, even though both are bad. A blocking call is one where control does not return to the calling user code until work is done. On the other hand, a function that takes a long time to execute will starve the event loop (just like an actual blocking call), but is not invoked directly by user code, and therefore is not preventing control from returning to the middle of a user function. To say that `nextTick` blocks is incorrect because control always returns from it immediately. – josh3736 Dec 03 '14 at 03:53
  • 1
    The only difference between `nextTick`, `setImmediate`, and `setTimeout` is *when* the callback is scheduled to run. Respectively, at the beginning of the next tick, at the end of the current event queue, and at some wall-time in the future. If the *callback* blocks the event loop by taking a long time to do work, it is said to starve the event loop because it prevents other events in the queue from being dispatched. Also, it only blocks the JS thread. The other (native) threads are not blocked; they continue to add events to the queue. – josh3736 Dec 03 '14 at 03:57
  • I agree about blocking vs starving, that's why I qualified it as "in nodejs"; I've only heard "blocking" used in this sense with node. As to the rest, you're not disagreeing with what I said, you're phrasing it more formally. I interpreted the use of nextTick in the code as wanting to yield, and pointed out that what it was actually doing was blocking everything else. I wasn't remarking on the semantics of nextTick, but on the code overall. +1 on the nice writeup by the way, that's good info to have out there, even I lose sight of the pthreads behind the scenes sometimes. – Andras Dec 03 '14 at 04:13
1

setImmediate will work better as explained in my blog post setTimeout and Friends as it will allow IO tasks to run before again locking up the main execution thread for a full run of compute. But as the other answers posted suggest, the way to think of this is not "nextTick doesn't work", but perhaps "oops I'm trying to do one of the only things you absolutely must not do in a node.js app and I'm getting the result I was warned about". You can't hog the execution thread in node as it is cooperative multitasking. Break computation into small chunks, use external process helpers, split something off into a supporting C++ library, etc.

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • to be fair to CodeFarmer, the load it seems to me was deliberate, to test concurrent processing. Except that javascript/node doesn't support that kind of parallelism. If the compute() loop looped only 1e6 times not 1e12, and used setImmediate() instead of nextTick, the experiment would have worked, the program would have both used 100% and and seamlessly handled requests at the "same time" – Andras Dec 03 '14 at 03:50
  • @Andras, no, using `setImmediate` would not solve the problem. `compute` would still starve I/O and the program would not be able to handle multiple requests at the same time. – josh3736 Dec 03 '14 at 04:00
  • see the part about 1e6 not 1e12 too, that would make compute() run in under a millisecond. Give me some credit, will you – Andras Dec 03 '14 at 04:16
1

I'll rewrite your code a little bit. Say we need to process 1000000s of items, and there is a (cpu bound, but ok to call sometimes) function computeItem() and io-bound postItem(). We want to process as much items as possible in the background, but still have responsive event loop. For simplicity, no external workers / queues / services used. Possible solution:

var desiredLatency = 10; // ms

function doBackgroundWork() {
  var start = new Date();
  var end;
  var item; 
  while (item = computeItem()) {
    postItem(item);
    if (end - start >= desiredLatency) {
      setImmediate(doBackgroundWork); // resume at next event loop invocation after processing other handlers
    }
  }
}

http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World');
}).listen(5000, '127.0.0.1');
doBackgroundWork();
Andrey Sidorov
  • 24,905
  • 4
  • 62
  • 75