JavaScript Concurrency Model

Dr. Greg Bernstein

Updated February 25th, 2020

Concurrency

Readings/References

JavaScript Runtime Model

From MDN:

Key Ingredients

  • Stack: Memory used for function calls. Stack frames consist of function arguments and local variables.

  • Heap: Memory used for all variables and objects.

  • Message/Event Queue: Mechanism for JavaScript concurrency.

Common Concurrency Mechanisms

  • Processes: Implemented by an OS, processes get separate protected memory spaces. Highest in overhead.

  • Threads: Implemented by runtime, OS, or library. Threads get separate program counters. Memory can be shared between threads. Not easy to program with in general.

  • Review Threads and Processes

Queue

From MDN:

“A JavaScript runtime uses a message queue, which is a list of messages to be processed. Each message has an associated function which gets called in order to handle the message.”

Event Loop

Psuedo-code model from MDN:

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

The previous code actually needs thread-like functionality to work.

Processing Events/Messages

  • The queue is ordered structure like a “waiting line”

  • Processing an event/message involves calling the callback function with parameters corresponding to the event or message.

  • The callback function is “run-to-completion”. This is known as non-preemptive scheduling. Review scheduling in OS

Adding Messages to the Queue

From MDN:

“In web browsers, messages are added anytime an event occurs and there is an event listener attached to it. If there is no listener, the event is lost.”

Example Events

  • DOM events
    1. GUI events
    2. Network on/offline, Websocket events
    3. And more…
  • Implicit Events (my terminology)
    1. When a fetch call returns.
    2. When a timer/interval expires.
    3. And more…

Make your Own Event

Using the setTimeout() function

  • var timeoutID = scope.setTimeout(callback[, delay, param1, param2, ...]);
  • Adds a “message” (the parameters), and callback function to the queue after delay in milliseconds.
  • delay is optional and defaults to 0.

Example

Modified from MDN:

console.log('this is the start');

setTimeout(function cb1() {
  console.log('this is a msg from call back 1');
});

console.log('this is just a message');

setTimeout(function cb2() {
  console.log('this is a msg from call back 2');
}, 0);

console.log('this is the end');

Results

Order and Asynchronous Operations

Example Node File Concatenation

const fs = require('fs');
const dirRoot = __dirname + "/sample_files/";
let myString = " Empty\n";

// Don't know which of the next three calls will execute first, could depend on file size.
fs.readFile(dirRoot+"samp1.txt", 'utf8', function(err, data){
  if (err) throw err;
  myString = data;
  console.log(data);
});

fs.readFile(dirRoot+"samp2.txt", 'utf8', function(err, data){
  if (err) throw err;
  myString += data;
  console.log(myString);
});

fs.readFile(dirRoot+"samp3.txt", 'utf8', function(err, data){
  if (err) throw err;
  myString += data;
  console.log(myString);
});

console.log("MyString: " + myString); // Executes first

Guaranteed In Order Processing

fs.readFile(dirRoot+"samp1.txt", 'utf8', function(err, data){
  if (err) throw err;
  myString = data + "\n";
  fs.readFile(dirRoot+"samp2.txt", 'utf8', function(err, data){
      if (err) throw err;
      myString += data + "\n";
      fs.readFile(dirRoot+"samp3.txt", 'utf8', function(err, data){
          if (err) throw err;
          myString += data + "\n";
          console.log(myString);  // Executes after all files have been read in order
        });
    });
});

console.log("MyString: " + myString); // Executes first

Issues

  • Ugly deeply nested code! And we didn’t deal with error conditions. When this gets deeper and more complicated it is known as “callback hell”.

  • If we were trying to be efficient how well did we achieve it?
    1. We allowed potentially other events to be processed (good)
    2. We didn’t allow the separate file reads to be processed at the same time.

Optimal Concatenation?

  • Have each of the file reads happen asynchronously then concatenate all the individual results.
  • Could you rewrite the program to do this?
    1. Remember each callback runs to completion
    2. Can set up indicator variables to let you know when individual files have been read.

Alternative Approach: JavaScript Promises

  • Provide “chaining” for “in order” processing

  • Provides an “all” method to start “tasks” in parallel and wait for them all to finish.

  • Provides a “race” method to start “tasks” in parallel and wait for the first one to finish.