Home > Mobile >  How does node.js schedule asynchronous and synchronous tasks?
How does node.js schedule asynchronous and synchronous tasks?

Time:06-21

I know how node.js executes code asynchronously without blocking the main thread of execution by using the event-loop for scheduling the asynchronous tasks, but I'm not clear on how the main thread actually decides to put aside a piece of code for asynchronous execution.

(Basically what indicates that this piece of code should be executed asynchronously and not synchronously, what are the differentiating factors?)

And also, what are the asynchronous and synchronous APIs provided by node?

CodePudding user response:

There is a mistake in your assumption when you ask:

what indicates that this piece of code should be executed asynchronously and not synchronously

The mistake is thinking that some code are executed asynchronously. This is not true.

Javascript (including node.js) executes all code synchronously. There is no part of your code that is executed asynchronously.

So at first glance that is the answer to your question: there is no such thing as asynchronous code execution.

Wait, what?

But what about all the asynchronous stuff?

Like I said, node.js (and javascript in general) executes all code synchronously. However javascript is able to wait for certain events asynchronously. There are no asynchronous code execution, however there is asynchronous waiting.

What's the difference between code execution and waiting?

Let's look an an example. For the sake of clarity I'll use pseudocode in a fake made-up language to remove any confusion from javascript syntax. Let's say we want to read from a file. This fake language supports both synchronous and asynchronous waiting:

Example1. Synchronously wait for the drive to return bytes form the file

data = readSync('filename.txt');
// the line above will pause the execution of code until all the
// bytes have been read

Example2. Asynchronously wait for the drive to return bytes from the file

// Since it is asynchronous we don't want the read function to 
// pause the execution of code. Therefore we cannot return the
// data. We need a mechanism to accept the returned value.

// However, unlike javascript, this fake language does not support
// first-class functions. You cannot pass functions as arguments
// to other functions. However, like Java and C   we can pass
// objects to functions

class MyFileReaderHandler inherits FileReaderHandler {
    method callback (data) {
        // process file data in here!
    }
}

myHandler = new MyFileReaderHandler()

asyncRead('filename.txt', myHandler);
// The function above does not wait for the file read to complete
// but instead returns immediately and allows other code to execute.
// At some point in the future when it finishes reading all data
// it will call the myHandler.callback() function passing it the
// bytes form the file.

As you can see, asynchronous I/O is not special to javascript. It has existed long before javascript in C and even C libraries dealing with file I/O, network I/O and GUI programming. In fact it has existed even before C. You can do this kind of logic in assembly (and indeed that's how people design operating systems).

What's special about javascript is that due to it's functional nature (first-class functions) the syntax for passing some code you want to be executed in the future is simpler:

asyncRead('filename.txt', (data) => {
    // process data in here
});

or in modern javascript can even be made to look like synchronous code:

async function main () {
    data = await asyncReadPromise('filename.txt');
}

main();

What's the difference between waiting and executing code. Don't you need code to check on events?

Actually, you need exactly 0% CPU time to wait for events. You just need to execute some code to register some interrupt handlers and the CPU hardware (not software) will call your interrupt handler when the interrupt occurs. And all manner of hardware are designed to trigger interrupts: keyboards, hard disk, network cards, USB devices, PCI devices etc.

Disk and network I/O are even more efficient because they also use DMA. These are hardware memory readers/writers that can copy large chunks (kilobytes/megabytes) of memory from one place (eg. hard disk) to another place (eg. RAM). The CPU actually only needs to set up the DMA controller and then is free to do something else. Once the DMA controller completes the transfer it will trigger an interrupt which will execute some device driver code which will inform the OS that some I/O event has completed which will inform node.js which will execute your callback or fulfill your Promise.

All the above use dedicated hardware instead of executing instructions on the CPU to transfer data. Thus waiting for data takes 0% CPU time.

So the parallelism in node.js has more to do with how many PCIe lanes your CPU supports than how many CPU cores it has.

You can, if you want to, execute javascript asynchronously

Like any other language, modern javascript has multi-threading support in the form of webworkers in the browser and worker_threads in node.js. But this is regular multi-threading like any other language where you deliberately start another thread to execute your code asynchronously.

  • Related