Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explaining Node Callbacks and Single Threading

Is node's javascript environment single threaded, or does everything happen at the same time? Or (more likely) do neither of those statements explain what's going on with node.

I'm new to node and trying to understand how it processes callbacks. My googling on the subject hasn't proven fruitful, and it seems like there's multiple audiences using terms like "threads, locking, and single threaded" with difference contexts for each audience, and I don't have enough experience with node to correctly parse what I'm reading.

From what I've read, node's javascript execution environment is, like a browser's, single threaded. That is, despite everything being designed around asynchronous callbacks, everything happens in a deterministic order, and there's never two threads modifying the same variable or running statements at the same time. I've also read this means a node programmer-user doesn't have to worry about locking semantics.

If I'm in browser-land and use one of the popular javascript libraries to setup a non-dom-event-handler callback, something like

console.log("Here");
$.each([1,2,3],function(){
    console.log("-- inside the callback --");
});
console.log("There");

My output is consistently

Here
-- inside the callback --
-- inside the callback --
-- inside the callback --
There

However, if I do something like this with a callback in node js (running it as a shell script from the command line)

var function example()
{
    var fs = require('fs'); 
    console.log("Here");
    fs.readdir('/path/to/folder', function(err_read, files){
        console.log('-- inside the callback --');            
    });
    console.log("There");
    for(var i=0;i<10000;i++)
    {
        console.log('.');
    }
}
example();
console.log("Reached Top");  

I consistently (seemingly — see "not much experience" above) get results like this

Here
There
.
. (repeat 10,000 times)
Reached Top
-- inside the callback --    

That is, node finishes executing the function example before calling the callback.

Is this deterministic behavior in node? Or are there times where the callback will be called before the example function finishes? Or will it depend on the implementation in the library using the callback?

I understand the idea behind node is to write event based code — but I'm trying to understand what node is really doing, and what behavior can be relied on and what can't.

like image 735
Alan Storm Avatar asked Aug 08 '13 22:08

Alan Storm


3 Answers

Is node's javascript environment single threaded, or does everything happen at the same time?

Node has single threaded program execution model, meaning that only one instruction will be executed at any time within one node process. The execution will continue until the program yields the control. This can happen at the end of the program code, or when callback reaches it's end.

In the first case:

console.log("Here");
$.each([1,2,3],function(){
    console.log("-- inside the callback --"); 
});
console.log("There");

they key is the fact that $.each uses callbacks synchronously, so effectively it calls it's callbacks in deterministic order.

Now in the second case, fs.readdir uses callbacks asynchronously - it puts the callback waiting for the event to be triggered (that is, when reading the directory finishes). That can happen at any time. The calling function, however, doesn't yield the control so example will always finish before any of the callbacks is called.

In one sentence: All the code of the function/global scope is executed before any callbacks defined in that function/global scope.

like image 83
soulcheck Avatar answered Sep 30 '22 13:09

soulcheck


In this case, you are calling a function that is doing IO and is asynchronous. That is why you are seeing output the way you are.

Because the rest of your code is executing inline - on the single execution thread, it is completed before the IO events are given a chance to make their way onto the event loop.

I would say expect this behavior, but don't depend on it with absolute certainty as it does depend on the implementation of the library using the callback. An (bad, evil) implementor could be making an synchronous request to see if there is any work to be done and, if not, callback immediately. (Hopefully you'll never see this, but…).

like image 20
dc5 Avatar answered Sep 30 '22 15:09

dc5


I would like to add a bit to @dc5's answer. On it's website they describe node as

Node.js uses an event-driven, non-blocking I/O model

The event driven part is very significant. It usually clears my doubts when I am having trouble understanding the execution model of the node programs.

So taking your example what is happening is :

  1. Here is printed to the console.

  2. An async call is made to the files.

  3. Node attaches a listener to the async call.

  4. Continues down the execution line and There is printed.

Now it could be that the async call is completed before the current piece of code that is being executed but node js completes the task at hand and then returns to service the async call.

So instead of waiting for anything it keeps on executing in line. Which is why node js execution loop is never idle.

like image 31
Akshat Jiwan Sharma Avatar answered Sep 30 '22 14:09

Akshat Jiwan Sharma