Multi-Threading with JavaScript Web Workers
Saturday, August 12th, 2017
Web Workers allow developers to create background threads which enables UI to run smoothly. This is useful for scenarios where the logical part of code is CPU intensive and it might slow down the UI.
Although the Web Workers can run code containing a wide variety of logic there are some restrictions that apply:
Dedicated Web Workers cannot be shared by other scripts so they are only accessible by the script that created them. In this blog post a worker will be created to generate Fibonacci sequence. Also the methods provided by the Worker object will be discussed.
Spawn:
A new Worker object can be spawned by calling the new Worker(<file URI>) constructor. The <file URI> is the location of the file relative to the origin.
Messaging:
The code run by the Worker cannot access functions from the caller script. So it has to communicate via messages.
The sample code above has two sections representing main.js and fiboanacci.js files. In each section there are methods that help the code communicate between two files.
In main.js file the listFibonacciNumbers method is used to send the Worker a message to start generating the numbers of size n . When the fibonacci number generation is completed the fiboanacci.js file sends back the array and fibWorker.onmessage callback will receive the array in main.js file. And any type of data can be sent back and forth between fiboanacci.js and main.js files using postMessage method. This bidirectional messaging helps the UI thread to sync with the Worker thread.
Termination:
Terminating a worker form the main thread is rather simple. When the worker is terminated all its operations will cease and it will no longer be active.
A worker can terminate itself by calling the following method from inside the worker code:
In contrast to dedicated worker the shared worker can be accessed from any script which has the same origin as the worker script. This allows multiple windows, iframes and other workers to access it.
Although the Web Workers can run code containing a wide variety of logic there are some restrictions that apply:
- The DOM cannot be manipulated from inside the Web Worker.
- The access to global variables and Objects(functions) from parent is restricted.
- Limited access to window object. Read-only for window.location property.
- No access to document and parent object.
- Newly spawned Web Workers can only be under the same origin as the parent.
Fig 1.0 Web Worker Types
There 5 Web Worker types that are laid out in Fig 1.0. But 'ChromeWorker' is specifc to only FireFox. The browser compatibility list can be found at http://caniuse.com/#feat=webworkers .
Although each Web Worker type has a crucial role to play in an application only Dedicated and Shared Workers have been discussed in this blog post.
Although each Web Worker type has a crucial role to play in an application only Dedicated and Shared Workers have been discussed in this blog post.
Dedicated Worker:
Dedicated Web Workers cannot be shared by other scripts so they are only accessible by the script that created them. In this blog post a worker will be created to generate Fibonacci sequence. Also the methods provided by the Worker object will be discussed.
Spawn:
let fibWorker;
if(window.Worker){
fibWorker = new Worker('fibonnaci.js');
}
A new Worker object can be spawned by calling the new Worker(<file URI>) constructor. The <file URI> is the location of the file relative to the origin.
Messaging:
Fig 2.0 Communication between UI and Worker threads
//main.js file
let listFibonacciNumbers = (n = 0) => {
fibWorker.postMessage({'number': n});
}
fibWorker.onmessage = (e) =>{
console.log('Fibonacci numbers:\n');
console.log(e.data.numbers);
}//------------------------------------------------------------------------------//
//fibonacci.jsonmessage = (e)=>{ let numbers = generateFibonacciNumbers(e.data.number); postMessage({'numbers':numbers});}let generateFibonacciNumbers = (size) => {
let n = size;
let arr = [];
for(let i =0; i<n ; i++){
arr[i] = i<2?1: arr[i-2]+arr[i-1];
}
return arr;
}
The sample code above has two sections representing main.js and fiboanacci.js files. In each section there are methods that help the code communicate between two files.
In main.js file the listFibonacciNumbers method is used to send the Worker a message to start generating the numbers of size n . When the fibonacci number generation is completed the fiboanacci.js file sends back the array and fibWorker.onmessage callback will receive the array in main.js file. And any type of data can be sent back and forth between fiboanacci.js and main.js files using postMessage method. This bidirectional messaging helps the UI thread to sync with the Worker thread.
Termination:
Terminating a worker form the main thread is rather simple. When the worker is terminated all its operations will cease and it will no longer be active.
fibWorker.terminate();
close();
Shared Worker :
In contrast to dedicated worker the shared worker can be accessed from any script which has the same origin as the worker script. This allows multiple windows, iframes and other workers to access it.
Fig 3.0 Communication between UI and Shared Worker threads
//main1.js
let inputNum = document.getElementById('fibnum');
if (!!window.SharedWorker) {
let fibWorker = new SharedWorker("worker.js");
inputNum.onchange = function() {
fibWorker.port.postMessage({'size': inputNum.value});
}
fibWorker.port.onmessage = function(e) {
console.log(e.data.fibArray);
}
}
//main2.js
let inputNum = document.getElementById('fibnum');
if (!!window.SharedWorker) {
let fibWorker = new SharedWorker("worker.js");
inputNum.onchange = function() {
fibWorker.port.postMessage({'size': inputNum.value});
}
fibWorker.port.onmessage = function(e) {
console.log(e.data.fibArray);
}
}
The sample codes above belong to two different files which are main1.js and main2.js . In this case the codes for the two files perform the same function of requesting fibonacci sequence array. But in general there can be many files that may ask a single worker to perform different tasks. UI thread can only communicate with the ServiceWorker via specific port. In main1.js and main2.js files the fibWorker.port.onmessage implicitly creates the port for communication. Using fibWorker.port.postMessage the main1.js and main2.js files can communicate with the worker. When the result is received from the worker the fibWorker.port.onmessage gets the generated fibonacci array.
//workers.js
let generateFibonacci = (size) => {
let n = size;
let arr = [];
for(let i =0; i<n ; i++){
arr[i] = i<2?1: arr[i-2]+arr[i-1];
}
return arr;
}
onconnect = (e) => {
let port = e.ports[0];
port.onmessage = function(e) {
let fibArray = generateFibonacci(e.data.size);
port.postMessage({arry: fibArray});
}
}
The code above belongs to the worker.js file. It has a method called generateFibonacci which takes in n as the argument to generate Fibonacci sequence of that size.The onconnect method automatically fires as soon as the worker is created. Then using port.onmessage event it can receive the desired size of fibArray so it can send back the generated array using port.postMessage method.