The wonderful environment of node.js uses an event loop rather than threading to deal with multiple incoming requests and more. Threading is tricky, or so I have been told. Event loops are less tricky, or so I have been told. Why?
I think the key reason is that threads are running at their own pace, separately. In event loops, until the loop loops, it is a single execution of logic. The idea is that there is a queue that takes in requests to act. Each time the current logic ends its execution, the queue is checked and the next action is taken, if any.
To demonstrate this, consider the simple node.js server, saved in server.js:
//server.js /*globals require, console, process*/ var http = require('http'); var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }); server.listen(1337, "127.0.0.1"); server.on('close', function () { console.log('server closed'); }); console.log('Server running at http://127.0.0.1:1337/'); // on exit, let us know. process.on('SIGINT', function () { console.log('server told to shut down'); server.close(); });
This server should work by running node server
. You should be able to send an interrupt with ctrl-c, at least on a Mac, initiating the close down procedures. Without the process.on
, a ctrl-c kills the server immediately.
Next we add a while loop:
//noserver.js /*globals require, console, process*/ var http = require('http'); var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }); server.listen(1337, "127.0.0.1"); server.on('close', function () { console.log('server closed'); }); console.log('Server running at http://127.0.0.1:1337/'); var count = 0; while (1) { if (count % 1000000 === 0) { console.log(count / 1000000 ); } count += 1; }
Running this will produce a non-functioning sever. Why? Because the while loop never cedes control. The event loop is never accessed. To make sure you can kill it with ctrl-c, we remove the process.on block first.1
To be explicit about loop access, we need to use process.nextTick()
:
//servertick.js /*globals require, console, process*/ var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, "127.0.0.1"); console.log('Server running at http://127.0.0.1:1337/'); var count = 0; process.nextTick(function self () { if (count % 1000000 === 0) { console.log(count / 1000000 ); } count += 1; process.nextTick(self); });
Notice how there is no evident loop. The loop is the event loop itself. Every time the anonymous function self
runs, it queues itself for the next loop. The function .nextTick
is a queueing agent. As far as I know, there is no way to cede direct control to the event loop. All one can do is do an explicit queue action and be done with the executing logic for the moment.
An analogue to .nextTick
in the browser is setTimeout(fn, 0)
which is what browserify does. But the docs in ndoe.js claim that nextTick is much more efficient than that and, thus, actually different. Let’s test this by running the following code:
/*globals require, console, process*/ var times = 10e6; var count = 0; var start = (new Date()).getTime(); while (count < times) { count += 1; } var diff = (new Date()).getTime() - start; console.log("while diff: "+diff+" count: "+count); start = (new Date()).getTime(); count = 0; process.nextTick(function self () { if (count < times ) { count += 1; process.nextTick(self); } else { diff = (new Date()).getTime() - start; console.log("nextTick diff: "+diff+" count: "+count); start = (new Date()).getTime(); count = 0; setTimeout(function setself () { if (count < times ) { count += 1; setTimeout(setself, 0); } else { diff = (new Date()).getTime() - start; console.log("setTimeout diff: "+diff+" count: "+count); start = (new Date()).getTime(); count = 0; process.nextTick(function lesstick () { var i; for (i = 0; i < 1e6; i += 1) { count += 1; } if (count < times ) { process.nextTick(lesstick); } else { diff = (new Date()).getTime() - start; console.log("delayed nextTick diff: "+diff+" count: "+count); } }); } }, 0); } });
This is not written particularly well. A better style would be to define the functions separately and then do the callbacks. Or you can use events such as with eventingfunctions. But as you can see we first do a while loop. This is fast. Then we use nextTick
callbacks. And we wait. Our third trial is setTimeout. We read a book. We come back and see the fourth trial uses nextTick, but does a million computations each time before releasing. This is fairly fast and will allow other stuff to happen. The results I obtained are2:
while diff: 34 count: 10000000 nextTick diff: 13840 count: 10000000 setTimeout diff: 109973 count: 10000000 delayed nextTick diff: 62 count: 10000000
As one can see, accessing the event loop is a costly procedure, but it is much better to use nextTick
than setTimeout
. For long running computations, a separate process is, of course, preferred. But at the least, use the trick in delayed NextTick
.
The code can be found at github.com/jostylr
{ 1 } Trackback
[…] other day I wrote about the event loop in node.js. In particular, there is a nice trick of ceding control to the event loop using […]
Post a Comment