Friday, April 8, 2016

HTML5-Web Workers

INTRODUCTION

In the browser, "normal" JavaScript code is run in a single thread (a thread is a light-weight process, see this Wikipedia page for details). This means that the user interface and other tasks are competing for processor time. If you run some intensive CPU tasks, everything is blocked, including the user interface. You have no doubt met this dialog during your Web browsing experiences.
With Internet Explorer:

ScriptNotResponding1

Or maybe:
script not responding2
The solution for this problem, as offered by HTML5, is to run some intensive tasks in other threads. By "other threads" we mean "threads different from the UI-thread, the one in charge of the graphic user interface". So, if you don't want to block the user interface, you can perform computationally intensive tasks in one or more background threads, using the HTML5 Web Workers. So, Web Workers = threads in JavaScript

AN EXAMPLE THAT DOES NOT USE WEB WORKERS

This example will block the user interface unless you close the tab. Try it at JSBin but DO NOT CLICK ON THE BUTTON unless you are prepared to kill your browser/tab, as this will eat all the CPU time, blocking the user interface: 
Examples that eats all the cpu
Code from the example:
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Worker example: One-core computation</title>
  5. </head>
  6. <body>
  7. <button id="startButton">Click to start discovering prime numbers</button><p> Note that this will make the page unresponsive, you will have to close the tab in order to get back your CPU!
  8. <p>The highest prime number discovered so far is: <output id="result"></output></p>
  9. <script>
  10.    function computePrime() {
  11.       var n = 1;
  12.       search: while (true) {
  13.          n += 1;
  14.          for (var i = 2; i <= Math.sqrt(n); i += 1)
  15.          if (% i == 0)
  16.          continue search;
  17.          // found a prime!
  18.          document.getElementById('result').textContent = n;
  19.       }
  20.     }
  21.     document.querySelector("#startButton").addEventListener('click',computePrime);
  22. </script>
  23. </body>
  24. </html>
Notice the infinite loop in the function computePrime (line 12, in bold). This will -for sure- block the user interface. And if you are brave enough to click on the button that calls the computePrime() function, you will notice that the line 18 execution (that should normally modify the DOM of the page and display the prime number that has been found) does nothing visible. The UI is unresponsive. This is really, really, bad JavaScript programming, and should be avoided at all costs.
Shortly we will see a "good version" of this example that uses Web Workers.

THREAD SAFETY PROBLEMS? NOT WITH WEB WORKERS!

When programming with multiple threads, a common problem is "thread safety". This is related to the fact that several concurrent tasks may share the same resources (i.e., JavaScript variables) at the same time. If one task is modifying the value of a variable while another one is reading it, this may result in some strange behavior. Imagine that thread number 1 is changing the first bytes of a 4 byte variable, and thread number 2 is reading it at the same time: the read value will be wrong (1st byte that has been modified + 3 bytes not yet modified).
With Web Workers, the carefully controlled communication points with other threads mean that it's actually very hard to cause concurrency problems. There's no access in a worker to non-thread safe components or to the DOM, and you have to pass specific data in and out of a thread through serialized objects. You share different copies so the problem with the four bytes variable explained in the previous paragraph cannot occur.
Different kinds of Web Workers:
There are two different kinds of Web Workers described in the specification:
    1. Dedicated Web Workers: threads that are dedicated to one single page/tab. Imagine a page with a given URL that runs a Web Worker that counts in the background 1-2-3- etc.  It will be duplicated if you open the same URL in two tabs for example, and each independent thread will start counting from 1 at startup time (when the tab/page is loaded).
    2. Shared Web Workers: these are threads that can be shared between different pages of tabs (they must conform to the same-origin policy) on the same client/browser. These threads will be able to communicate, exchange messages, etc. For example, a shared worker, that counts in the background 1-2-3- etc. and sends the current value to a page/tab, will display the same value in all pages/tab that share a communication channel with it, and if you refresh all the pages, they will display the same value. The pages don't need to be the same (with the same URL), however they must conform to the "same origin" policy.
Shared Web Workers will not be studied in this course. They are not yet supported by major browser vendors, and their complete study would require a whole week's worth of study material. We may cover this topic in a future version of this course, when implementations are more stable/available.

EXTERNAL RESOURCES

CURRENT SUPPORT

Dedicated Web Workers

Support as at December 2015:
dedicated web workers support
Up to date version of this table: http://caniuse.com/#feat=webworkers

Shared Web Workers (not studied), only in Chrome and Opera so far:

Shared web workers support


Typical use of Web Workers

1 - YOU CREATE WORKERS FROM A SCRIPT EXECUTED BY A "PARENT HTML5 PAGE"

The HTML5 Web Worker API provides the Worker JavaScript interface for loading and executing a script in background, in a thread different from the UI thread. The following instruction does exactly that: loading and creating a worker:
  1. var worker = new Worker("worker0.js");
More than one worker can be created/loaded by a parent page. This is parallel computing after all :-)

2 - YOU USE A WORKER BY COMMUNICATING WITH IT USING MESSAGES

Messages can be strings or objects, as long as they can be serialized in JSON format (this is the case for most JavaScript objects, and is handled by the Web Worker implementation of recent browser versions).
Messages can be sent to a worker using this kind of code:
  1. var worker = new Worker("worker0.js");
  2. // String message example
  3. worker.postMessage("Hello");
  4. // Object message example
  5. var personObject = {'firstName': 'Michel', 'lastName':'Buffa'};
  6. worker.postMessage(personObject );
Messages can be received from a worker using this kind of code (code located in the JavaScript file of the worker):
  1. worker.onmessage = function (event) {
  2.    // do something with event.data
  3.   alert('received ' + event.data.firstName);
  4. };
The worker can also communicate messages back to the parent page:
  1. postMessage("Message from a worker !");
And a worker can listen to messages from the parent page like this:
  1. onmessage = function(event){
  2.     // do something with event.data
  3. };

3 - A COMPLETE, VERY SIMPLE, EXAMPLE

The HTML page of the most simple example of dedicated Web Workers:
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Worker example: One-core computation</title>
  5. </head>
  6. <body>
  7. <p>The most simple example of Web Workers</p>
  8. <script>
  9.    // create a new worker (a thread that will be run in the background)
  10.    var worker = new Worker("worker0.js");
  11.    // Watch for messages from the worker
  12.    worker.onmessage = function(e){
  13.      // Do something with the message from the client: e.data
  14.      alert("Got message that the background work is finished...")
  15.    };
  16.    // Send a message to the worker
  17.    worker.postMessage("start");
  18. </script>
  19. </body>
  20. </html>
The JavaScript code of the worker:
  1. onmessage = function(e){
  2.    if ( e.data === "start" ) {
  3.       // Do some computation that can last a few seconds...
  4.       // alert the creator of the thread that the job is finished
  5.       done();
  6.     }
  7. };
  8. function done(){
  9.     // Send back the results to the parent page
  10.     postMessage("done");
  11. }

4 - HANDLING ERRORS

The parent page can handle errors that may occur in the workers that it created by listening to the onError event on the worker objects:
  1. var worker = new Worker('worker.js');
  2. worker.onmessage = function (event) {
  3.     // do something with event.data
  4. };
  5. worker.onerror = function (event) {
  6.     console.log(event.message, event);
  7. };
  8. }
See also the section "how to debug Web Workers"...


Examples of Web Workers

Dedicated Workers are the simplest kind of Workers. Once created, they are linked to their parent page (the HTML5 page that created them). There is an implicit "communication channel" opened between the Workers and the parent page, so that messages can be exchanged.

FIRST EXAMPLE: COMPUTE PRIME NUMBERS IN THE BACKGROUND WHILE KEEPING THE PAGE USER INTERFACE RESPONSIVE

Let's look at the first example, taken from the W3C specification"The simplest use of workers is for performing a computationally expensive task without interrupting the user interface. In this example, the main document spawns a worker to (naĆÆvely) compute prime numbers, and progressively displays the most recently found prime number."
This is the example we tried earlier, without Web Workers, that froze the page. This time it uses Web Workers. Notice that, unlike the previous example, it will display the prime numbers it has computed at regular intervals.
Try this example online (we cannot put it on JsBin as Workers need to be defined in a separate JavaScript file) : 
Prime nulmber computation
The HTML5 page code from this example that uses a Web Worker:
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Worker example: One-core computation</title>
  5. </head>
  6. <body>
  7. <p>The highest prime number discovered so far is: <output id="result"></output></p>
  8. <script>
  9.     var worker = new Worker('worker.js');
  10.     worker.onmessage = function (event) {
  11.         document.getElementById('result').textContent = event.data;
  12. };
  13. </script>
  14. </body>
  15. </html>
In this source code, the Web Worker is created at line 9, and its code is in the worker.js file. Lines 10-12process a message sent asynchronously by the worker: event.data is the message content. Workers can only communicate with their parent page using messages. See the code of the worker below to see how the message has been sent.
The code of the worker (worker.js):
  1. var n = 1;
  2. search: while (true) {
  3.     n += 1;
  4.     for (var i = 2; i <= Math.sqrt(n); i += 1)
  5.     if (% i == 0)
  6.     continue search;
  7.     // found a prime!
  8.     postMessage(n);
  9. }
There are a few interesting things to note here:
    1. There is an infinite loop in the code at line 2 (while true...). This is not a problem as this code is run in the background.
    2. When a prime number is found, it is "posted" to the creator of the Web Worker (aka the HTML page), using the postMessage(...) function.
    3. Computing prime numbers by using such a bad algorithm is very CPU intensive. However, the Web page is still responsive; you can refresh it, the dialog "script not responding" does not appear, etc. There is a demo at the end of this chapter in which some graphic animation has been added to this example, and you can verify that the animation is not affected by the computations in the background.

TRY AN IMPROVED VERSION OF THE FIRST EXAMPLE YOURSELF

We can improve this example a little by testing whether the browsers support Web Workers, and displaying some additional messages.
CAREFUL: for security reasons you cannot try the examples using a file:// URL. You need an HTTP web server that will serve your files. Here is what happens if you do not follow this constraint:
using file URL does not work for security reasons
This occurs with Opera, Chrome and Firefox. With Chrome, Safari or Chromium, you can run the browser using some command line options to override these security constraints. Read, for example, this blog post that explains this method in detail.
Ok, back to our improved version! This time, we test if the browser supports Web Workers, and we also use a modified version of the worker.js code for displaying a message, and wait 3 seconds before starting the computation of prime numbers.
You can download this example: WebWorkersExample1.zip
HTML code:
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Worker example: One-core computation</title>
  5. </head>
  6. <body>
  7. <p>The highest prime number discovered so far is: <output id="result"></output></p>
  8. <script>
  9.     if(window.Worker){
  10.        // web workers supported by the browser
  11.        var worker=new Worker("worker1.js");
  12.        worker.onmessage=function(event){
  13.           document.getElementById('result').textContent = event.data;
  14.        };
  15.     }else{
  16.        // the browser does not support web workers
  17.        alert("Sorry, your browser does not support Web Workers");
  18.     }
  19. </script>
  20. </body>
  21. </html>
Line 9 shows how to test if the browser can run JavaScript code that uses the HTML5 Web Workers API.
worker1.js code:
  1. postMessage("Hey, in 3s, I'll start to compute prime numbers...");
  2. setTimeout(function() {
  3.     // The setTimeout is just useful for displaying the message in line 1 for 3 seconds and
  4.     // making it visible
  5.     var n = 1;
  6.     search: while (true) {
  7.        n += 1;
  8.        for (var i = 2; i <= Math.sqrt(n); i += 1)
  9.        if (% i == 0)
  10.        continue search;
  11.        // found a prime!
  12.        postMessage(n);
  13.      }
  14. }, 3000);
In this example, we just added a message that is sent to the "parent page" (line 1) then the standard JavaScript method setTimeout() is used for delaying the beginning of the prime number computation by 3s.

SECOND EXAMPLE: HOW TO STOP/KILL A WORKER AFTER A GIVEN AMOUNT OF TIME

So far we have created and used a worker. Now we will see how to kill it!
A worker is a thread, and a thread uses resources. If you no longer need its services, it is best practice to release the used resources, especially since some browsers may run very badly when excessive memory consumption occurs. Even if we unassign the variable that was used to create the worker, it continues to live, it does not stop! Worse: the worker becomes inaccessible but still exists (and therefore the memory is still used), and we cannot do anything but close the tab/page/browser.
The Web Worker API provides a terminate() method that we can use on any worker, ending its life. After a worker has been killed, it is not possible to undo its execution. The only way is to create a new worker.
HTML code:
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <title>Worker example: One-core computation</title>
  5. </head>
  6. <body>
  7. <p>The highest prime number discovered so far is: <output id="result"></output></p>
  8. <script>
  9.     if(window.Worker){
  10.        // web workers supported by the browser
  11.        var worker=new Worker("worker2.js");
  12.        worker.onmessage=function(event){
  13.            document.getElementById('result').textContent = event.data;
  14.        };
  15.     }else{
  16.        // the browser does not support web workers
  17.        alert("Sorry, your browser does not support Web Workers");
  18.     }
  19.     setTimeout(function(){
  20.        // After 10 seconds, we kill the worker
  21.        worker.terminate();
  22.        document.body.appendChild(document.createTextNode("Worker killed, 10 seconds elapsed !")
  23.     );}, 10000);
  24. </script>
  25. </body>
  26. </html>
Notice at line 22 the call to worker.terminate(), that kills the worker after 10000ms.
worker2.js is the same as in the last example:
  1. postMessage("Hey, in 3s, I'll start to compute prime numbers...");
  2. setTimeout(function() {
  3.      // The setTimeout is just useful for displaying the message in line 1 for 3 seconds and
  4.      // making it visible
  5.      var n = 1;
  6.      search: while (true) {
  7.          n += 1;
  8.          for (var i = 2; i <= Math.sqrt(n); i += 1)
  9.          if (% i == 0)
  10.          continue search;
  11.          // found a prime!
  12.          postMessage(n);
  13.      }
  14. }, 3000);

A WEB WORKER CAN INCLUDE EXTERNAL SCRIPTS

External scripts can be loaded by workers using the importScripts() function.
worker.js:
  1. importScripts('script1.js');
  2. importScripts('script2.js');
  3. // Other possible syntax
  4. importScripts('script1.js', 'script2.js');
The included scripts must follow the same-origin policy.
The scripts are loaded synchronously and the function importScripts() doesn’t return until all the scripts have been loaded and executed. If an error occurs during a script importing process, a NETWORK_ERROR is thrown by the importScripts function and the code that follows won’t be executed.

LIMITATIONS OF WEB WORKERS

Debugging threads may become a nightmare when working on the same object (see the "thread security" section at the beginning of this page). To avoid such a pain, the Web Workers API does several things:
    1. When a message is sent, it is always a copy that is received: no more thread security problems.
    2. Only predefined thread-safe objects are available in workers, this is a subset of those usually available in standard JS scripts.
Objects available in Web Workers:
    • The navigator object
    • The location object (read-only)
    • XMLHttpRequest
    • setTimeout()/clearTimeout() and setInterval()/clearInterval()
    • The Application Cache
    • Importing external scripts using the importScripts() method
    • Spawning other Web Workers
Workers do NOT have access to:
    • The DOM (it's not thread-safe)
    • The window object
    • The document object
    • The parent object
WOW! This is a lot! Son, please be careful!
We borrowed these two figures from a MSDN blog post (in French), as they illustrate this very well:
web worker scope
Note that:
    1. Chrome has already implemented a new way for transferring objects from/to Web Workers by reference, in addition to the standard "by copy" method. This is in the HTML 5.1 draft specification from the W3C - look for "transferable" objects! 
    2. The canvas is not usable from Web Workers, however, HTML 5.1 proposes a canvas proxy. 

DEBUGGING WEB WORKERS

Like other multi-threaded applications, debugging Web Workers can be a tricky task, and having good instruments makes this process much easier.
When you open a page with Web Workers, open the Chrome Dev Tools (F12), look on the right at the Workers tab, check the radio box and reload the page. This will pop a small window for tracing the execution of each worker. In these windows you can set breakpoints, inspect variables, log messages, etc. Here is a screenshot of a debugging session with the prime numbers example:
chrome debug web workers
    • IE 11 has some interesting debugging tools, too:
See this MSDB blog post for details. Load a page with Web Workers, press F12 to show the debugging tools. Here is a screenshot of a debugging session with Web Workers in IE11:
IE 10 debugging web workers
web workers IE10 second screenshot

Interesting demos that use Web Workers

DEMO 1:

Variation of the prime number demo that shows that an animation in the parent page is not affected by the background computation of prime numbers. Try it online: http://html5demos.com/worker
Move the blue square with up and down arrows, it moves smoothly. Click the "start worker" button: this will run the code that computes prime numbers in a Web Worker, and try to move the square again: the animation hasn't even slowed down...
prime number and animation

DEMO 2

Do ray tracing using a variable number of Workers, and try it online: http://nerget.com/rayjs-mt/rayjs.html
In this demo, you can select the number of Web Workers that will compute parts of the image (pixels). If you use too many Web Workers, the performance decreases: too much time is spent exchanging data between workers and their creator instead of computing in parallel.
ray tracer with web workers

OTHER DEMOS

Many impressive demos at the Mozilla Developer Network

There are also many impressive demos at Chrome Experiments



No comments:

Post a Comment