Table of Contents
- Introduction
- Web Workers
- The Problem
- Code Descriptions
- Other Applications
- Conclusions
Introduction
In this second installment of our series on developing HTML5
applications for AppUp, we'll look at a new feature of HTML5, Web Workers.
We'll present an application that illustrates the use of Web Workers, as
well as some other HTML5 features such as Canvas. We conclude with a discussion
of other possible applications for Web Workers.
All of the source files described in this article can be
downloaded from here.
Web Workers
Normally JavaScript code running in a browser executes in a
single thread. If you need to perform some type of CPU intensive calculations,
this can result in the JavaScript code running slowly and affecting the
responsiveness for the user. Being able to run some JavaScript code in another
thread or background process would solve this problem. Furthermore, modern
multicore CPUs can execute multiple threads in parallel, so we would like to
support multiple threads to make use of the hardware's capabilities.
Web Workers is a new HTML5 feature that supports multiple
threads of execution. A Web Worker is a thread which allows you to run tasks in
a background process in parallel with the main browser UI thread. A Web Worker
is a JavaScript file which contains the task you want to perform in a separate
thread. Web Workers allows you to do things like handle computationally
intensive tasks without blocking the UI.
A common application of Web Workers is performing a
computationally expensive task without interrupting the user interface. They
can also be used for time consuming operations like network operations.
Multithreading by its very nature raises potential
synchronization problems for shared data that is accessed by multiple threads.
Because of this, Web Workers imposes some restrictions, most notably
restricting access to the DOM, window object, document object, and parent
object by Web Workers.
The example application in this article illustrates using
Web Workers to perform a CPU intensive operation, specifically, calculating
prime numbers.
The Problem
Recall that a prime number is a number greater than one that
has no positive divisors other than one and itself. Prime numbers are important
in many areas of computing such as encryption, but for our purposes it makes a
good example of a CPU intensive calculation that can illustrate the use of Web
Workers.
A simple method of determining if a number is prime is known
as trial division. It consists of testing whether a given number n is a
multiple of any integer between 2 and the square root of n. If the remainder
after dividing n by the trial integer is zero, then n is not prime. There are
algorithms that are much more efficient than trial division, but for our
purposes it makes a good CPU-intensive problem to apply Web Workers to.
Our example application will compute prime numbers and
display the most recently found prime. We'll also show the elapsed time since
the calculations were started. As there is an infinite number of prime numbers,
the program will never complete. We'll provide a Stop button to stop the
calculations and a Close button to exit the application.
To show that the calculations are indeed happening in a
separate thread, we will show an animation of a clock using the HTML5 Canvas
feature.
Code Description
To start, we need a standard icon file named icon.png to
satisfy the Intel AppUp™encapsulator.
We use a style sheet to get the look we want for buttons,
the text font for the results, etc. You can study this file app.css at your
leisure.
Execution starts with the file index.html. As shown in
Listing 1, we include two JavaScript files: stopwatch.js and main.js:
<!DOCTYPE HTML>
<html>
<head>
<title>Web Workers</title>
<link href="app.css" rel="stylesheet" type="text/css" />
<script src="stopwatch.js" type="text/javascript"></script>
<script src="main.js" type="text/javascript"></script>
</head>
Listing 1: File index.html Part 1
Then we set a background image and call a JavaScript
function to initially draw the stopwatch, and then declare some variables that
we will be using:
<body background="numbers.jpg" onload="stopwatch();">
<script type="text/javascript">
var running = false;
var worker;
var timerId = 0;
var seconds = 0;
var date;
</script>
Listing 2: File index.html Part 2
The remainder of the file displays the UI strings, defines a
canvas that we will use to draw an animated stopwatch, and defines the Start,
Stop, and Close buttons, associating them with the JavaScript functions to run
when they are clicked. Note that the elapsed time and highest prime number
results use <output>
tags. We'll see shortly how they are hooked up to
the code that produces the results.
<h1>Web Workers Example: Prime Numbers</h1>
<hr>
<h3>Press the Start button to start the calculation process.<br>
Press the Stop button to stop calculating.</h3>
<canvas id="stopwatch" width=150 height=150>Your browser doesn't support HTML5 canvas</canvas>
<h3>Elapsed time: <output id="elapsedTime"></output><br>
Highest prime number found: <output id="primeNumber"></output></h3>
<hr>
<br>
<a href="#" id="Start" class="button white" onclick="startWorker()">Start</a>
<a href="#" id="Stop" class="button white" onclick="stopWorker()">Stop</a>
<a href="#" id="Close" class="button white" onclick="closeApplication()">Close</a>
</body>
</html>
Listing 3: File index.html Part 3
Now let's look at the code for main.js, shown in Listing 4
below. The functions startWorker is called when the Start button is clicked. If
the worker thread is not already running, it sets the variable running to true,
elapsed time to zero, and saves the current date and time. It calls init()
from
stopwatch.js which we will look at later.
We then create a new Worker, the JavaScript Web Worker type.
We add an event listener for it, and set the message handler to be the function
e which we will see shortly. We set the events to go to the output tag we
defined in main.html using its identifier primeNumber
. We get the current time
and write it into the output tag field that we defined in main.html, using its
identifier elapsedTime
. Finally, we post a message to the worker to get it
started.
function startWorker()
{
if (!running)
{
running = true;
seconds = 0;
date = new Date;
init();
worker = new Worker("worker.js");
worker.addEventListener('message', onmessage, false);
worker.onmessage = function(e)
{
document.getElementById('primeNumber').textContent = event.data;
date.setHours(0);
date.setMinutes(0);
date.setSeconds(seconds - 1);
document.getElementById('elapsedTime').textContent = date.toTimeString("hh:mm:ss").substring(0, 8);
}
worker.postMessage(); }
}
function stopWorker()
{
if (running)
{
running = false;
clearInterval(timerId);
worker.terminate(); }
}
function closeApplication()
{
intel.adp.encapsulator.closeapplication()
}
Listing 4: File main.js
The stopWorker
functions is called when the Stop button is
clicked. It sets the variable running to false and stops a timer (which we
created when init()
was called, as we'll see later). It then terminates the Web
Worker. This is important as there are resources that a Web Worker uses that
are freed only when it is terminated. The closeApplication
function, called by
the Close button, simply closes the application.
Now let's look at the file worker.js, shown in listing 5.
The message handler function in this file is run in the background by a
separate thread from the main GUI JavaScript thread. It searches for prime
numbers starting from 2 using the method of trial division. When a prime number
is found, it posts a message passing the number as an argument. That gets
written to the output field we set up in main web page.
addEventListener('message', onmessage, false);
onmessage = function(e)
{
var n = 1;
search:
while (true)
{
n += 1;
for (var i = 2; i <= Math.sqrt(n); i += 1)
if (n % i == 0)
continue search; postMessage(n);
}
}
Listing 5: File worker.js
The final file is stopwatch.js, shown in Listing 6. The
first function in the file is init. This was called from startWorker
. It first
calls the function stopwatch and then creates a timer that will call function
stopwatch every second.
The other function in the file, stopwatch, draws an animated
clock with a hand showing seconds. It uses the canvas element created earlier
to draw. The location of the watch hand is drawn using the values of seconds
that was earlier calculated based on the current time. The code is somewhat
long but is straightforward. You can study it at your leisure.
function init()
{
stopwatch();
timerId = setInterval(stopwatch, 1000);
}
function stopwatch()
{
var elem = document.getElementById('stopwatch');
if (elem && elem.getContext)
{
var context = elem.getContext('2d');
if (context)
{
context.save();
context.clearRect(0, 0, 150, 150);
context.translate(75, 75);
context.scale(0.5, 0.5);
context.rotate(-Math.PI / 2);
context.strokeStyle = "grey";
context.fillStyle = "white";
context.lineWidth = 6;
context.lineCap = "square";
context.save();
context.lineWidth = 5;
for (i = 0; i < 60; i++)
{
if (i % 5 != 0)
{
context.beginPath();
context.moveTo(120, 0);
context.lineTo(115, 0);
context.stroke();
}
context.rotate(Math.PI / 30);
}
context.restore();
context.save();
for (var i = 0; i < 12; i++)
{
context.beginPath();
context.rotate(Math.PI / 6);
context.moveTo(120, 0);
context.lineTo(100, 0);
context.stroke();
}
context.restore();
context.fillStyle = "black";
context.save();
context.rotate(seconds++ * Math.PI / 30);
context.lineWidth = 6;
context.lineCap = "round";
context.strokeStyle = "red";
context.beginPath();
context.moveTo(-30, 0);
context.lineTo(100, 0);
context.stroke();
context.beginPath();
context.arc(0, 0, 10, 0, 2 * Math.PI, false);
context.fill();
context.restore();
context.beginPath();
context.strokeStyle = "#417FA1";
context.lineWidth = 10;
context.arc(0, 0, 140, 0, 2*Math.PI, false);
context.stroke();
context.restore();
}
}
}
Listing 6: File stopwatch.js
Figure 1 shows a screen shot of the application running:
Figure 1: The Running Application
As there are an infinite number of primes, the algorithm
will never complete. Our example program will eventually fail when the values
of the numbers it uses get so large that the JavaScript representation of a
floating point number loses precision such that incrementing the value by one
returns the same value. This won't happen until after many hours of execution.
Other Applications
For a CPU intensive application, you may think that using an
interpreted language like JavaScript might be slow, but performance can be
surprisingly good with modern JavaScript engines. As a very simple benchmark, I
timed how long it took the the Web Workers application to calculate the primes
up to 10 million. I compared that to a native C++ program that used the same
algorithm to calculate primes. The HTML5 application took about 2 minutes to
calculate the primes between 1 and 10 million. The C++ program took about 35
seconds on the same hardware. It was a somewhat unfair comparison because the
HTML5 program also needed to perform output of the results in a nice format.
Even so, the JavaScript code was still within the same order of magnitude of
performance as the native code.
If you need to optimize performance using native code, one
alternative is a hybrid application that has both browser (HTML5) code and
native code. This, of course, will sacrifice portability.
Some other areas where Web Workers could be useful are the
following:
-
performing network input/output in the background
-
rich text (e.g source code) syntax highlighting
-
image processing of data extracted from the
<canvas>
or
<video>
elements
-
updating a client-side database
Conclusions
In this article we introduced the HTML Web Workers feature.
We showed a simple example application using Web Workers and discussed some
more realistic real-life examples of where they could be applied.