I’ve recently decided to start these series, so please expect more to come on regular basis.
There are couple of functions, related to time — which I’d like to remind you quickly:
getTime() — returns, the current, world time counted in milliseconds since the 1st of January 1970 00:00:00
new Date() — returns a basic Date object, the trick is — constructor sets its value to the current time from operating system
process.hrtime() / window.performance.now() — more detailed functions for receiving current time — detailed down to nanoseconds
setTimeout() and setInterval() — manipulate current application with delaying and timer operations.
In the next few tests I’ll be showing certain examples when especially setTimeout.
How I thought setTimeout should work:
Imagine the very basic function func scheduled by setTimeout at time zero. Our whole program as well as the the func function are really small — so we expect the application runtime will be idle for most of the time and when 2000ms will pass — it will be ready-steady to launch.
How setTimeout really works:
Sadly — even for such a simple application, keeping in mind my MacBook was pretty idle and has Intel i5, 2.9 GHz CPU available (one operation takes ~0.3448 nanosecond = 10-9s) — the delay was in 10-2s to 10-4s, so almost 5–7 orders of magnitude.
Actually in my terminal using NodeJS the delay was even bigger:
Can you see it? 2.74 seconds! Instead of 2 seconds. Mindblown!
How I thought setInterval should work:
The goal of the setInterval function is to keep on running a predefined function unlimited number of times. I imagine if my MacBook is pretty idle and the interval is about 200ms, called function func is also lightweight — assuming the application started at zero time, every second the func will be called 5 times, every minute 300 times etc.
How setInterval really works:
I’ve created a very simple function for NodeJS:
So after 159 very low-complexity function calls we’re half a second after the schedule.
Ok, so let’s play a bit with combinations of timers:
- code running delays — just time spent on calculations
- interval timer
- timeout timer
In other words — uestion is: what will happen if the computation time of our function is considerably longer that the timer itself. Let’s take a deeper look on some example — I’ve defined a function that runs almost 2 seconds on my computer — for that purpose I’ve used a dummy loop with 999999999 iterations;
Then I’ve declared a repetition of this function using a very short, 200ms intervals:
I’ve also created a short table of absolute times for each execution:
I’ve also managed to prepare an extreme example — where the 5 millisecond interval is blocked for 60 seconds 🙂 So if you have a heavy enough computation — your timers will just get lower priority and need to wait.
Findings (EDIT — wrong, see below):
* scroll few lines below for a disclaimer
TL;DR You need to understand your tools and use them wisely.
Search for a good article on Web Workers. Maybe I’ll put one here.
POST-EDIT-DISCLAIMER (added 24th April 2017):
The main goal for the concurrency in programming is to run code faster and avoid waiting, using programming techniques and available hardware resources. There are 2 main strategies to run code faster:
- asynchronous — using event-loop
- multi-threading — using multiple call stacks
- system / Web APIs
Call stack executes its functions in a FIFO manner. Event-loop will take a look on the call stack and whenever it’s empty — it will put there a first task from the callback-queue. System / Web APIs are responsible for operations that will can done by system / browser resources, for example:
- I/O callbacks (reading files)
- timers (setTimeout, setInterval)
- networking (xmlHttpRequest)
There is good video that explains this:
In the multithreading model, every thread has its own call stack and the threads are being run in parallel, so there’s no need for the event-loop. The main disadvantage for the multithreaded code is the management of the the communication between threads. It can be really difficult and requires extra code, usually a bottom-up architecture reorganization.
You can use both approaches. In order to achieve asynchronous multithreading in JS you can use Web Workers.
To sum up — there are 4 main types of approaches towards concurrency issues:
- Single-threaded synchronous (JS call stack)
- Single-threaded asynchronous (JS runtime)
- Multi-threaded synchronous (for example: Clojure)
- Multi-threaded asynchronous (JS runtime with Web Workers*)
* language features are limited in JS Web Workers (like DOM access)