• No results found

A SINGLE-THREADED WORLD

It’s important to note that Node uses a single thread of execution. It’s not possible, without the help of third-party modules, to change this fact.

To illustrate what this means and how it relates to the event loop, consider the following example:

var start = Date.now();

setTimeout(function () {

console.log(Date.now() - start);

for (var i = 0; i < 1000000000; i++){} }, 1000);

setTimeout(function () {

console.log(Date.now() - start); }, 2000);

These two timeouts print how many seconds elapsed from the moment they’re set to the moment the functions are called. The output in my computer looks like Figure 3-1.

Figure 3-1: This program shows the elapsed time when each setTimeout is executed, which doesn’t correlate to the values in the code.

What happens internally is that the event loop is blocked by the JavaScript code. When the first event is dispatched, the JavaScript callback is run. Because you are doing a lot of intense computation (a very long for loop), by the time the next iteration of the event loop is

executed, more than two seconds have elapsed; therefore, the JavaScript timeouts don’t match actual clock seconds.

32

PA R T I • Getting Started: Setup and Concepts

This behavior is, of course, undesirable. As I explained previously, the event loop is the foundation of all IO in Node. If a timeout can be delayed, so can an incoming HTTP request or other forms of IO. That means the HTTP server would handle fewer requests per second and not perform efficiently.

For this reason, the great majority of modules available for node are non-blocking and perform tasks asynchronously.

If you have only one thread of execution, which means that as a function is running no others can be executed concurrently, how is Node.JS so good at managing a lot of network concur- rency? For example, in a normal laptop, a simple HTTP server written in Node is able to handle thousands of clients per second.

For this to happen, you must first understand the concept of call stacks.

When v8 first calls a function, it starts what is commonly known as a call stack or execution stack.

If that function calls another function, v8 adds it to the call stack. Consider the following example:

function a () { b();

}

function b(){};

The call stack in this example is composed of “a” followed by “b”. When “b” is reached, v8 doesn’t have anything left to execute.

Return to the HTTP server example:

http.createServer(function () { a(); }); function a(){ b(); }; function b(){};

In this example, whenever an HTTP client connects to Node, the event loop dispatches a notification. Eventually, the callback function is executed, and the call stack becomes “a” > “b”. Since Node is running in a single thread, while that call stack is being unrolled no other client or HTTP request can be handled.

You might be thinking, then, that Node maximum concurrency is 1! And that would be correct. Node does not offer true parallelization, because that would require the introduction of many parallel threads of execution.

C H A P T E R 3 • Blocking and Non-blocking IO

33

The key is that you don’t need to handle more than one at the same given instant, provided that the call stack executes really fast. And that’s why v8 coupled with non-blocking IO are so good together: v8 is really fast at executing JavaScript, and non-blocking IO ensures the single thread of execution doesn’t get hung up on external uncertainties, like reading a database or hard disk.

A real-world example of the utility of non-blocking IO is the cloud. In most cloud deploy- ments like the Amazon cloud (“AWS”), operating systems are virtualized and hardware resources are shared between tenants (since you are essentially “renting hardware”). What this means is that if the hard drive, for example, is spinning to seek a file for another tenant, and you are trying to seek, the latency will increase. Since the IO performance for the hard drive is very unpredictable, if we blocked our thread of execution when we’re reading a file, our program could behave very erratically and slowly.

A common example of IO in our applications is getting data from databases. Imagine a situation where you need to get some data from the database to respond to a request.

http.createServer(function (req, res) { database.getInformation(function (data) { res.writeHead(200);

res.end(data); });

});

In this case, once a request comes in, the call stack is just composed of the database call. Since the call is non-blocking, it’s up to the event loop once again to initiate a new call stack when the database IO completes. But after you tell Node “let me know when you have the database response,” Node can continue to do other things. Namely, handling more HTTP clients and requests!

A topic covered throughout the book that very much has to do with the way Node is archi- tected is error handling, described next.