Web Workers are, undoubtedly, the coolest new feature to arrive in the latest version of web browsers. Web Workers allow you to run JavaScript in parallel on a web page, without blocking the user interface.
Normally in order to achieve any sort of computation using JavaScript you would need to break your jobs up into tiny chunks and split their execution apart using timers. This is both slow and unimpressive (since you can’t actually run anything in parallel – more information on this in How JavaScript Timers Work).
With our current situation in mind, let’s dig in to Web Workers.
Web Workers
The Web Worker recommendation is partially based off of the prior work done by the Gears team on their WorkerPool Module. The idea has since grown and been tweaked to become a full recommendation.
A ‘worker’ is a script that will be loaded and executed in the background. Web Workers provide a way to do this seamlessly, for example:
new Worker("worker.js");
The above will load the script, located at ‘worker.js’, and execute it in the background.
There are some HUGE stipulations, though:
- Workers don’t have access to the DOM. No
document
,getElementById
, etc. (The notable exceptions aresetTimeout
,setInterval
, andXMLHttpRequest
.) - Workers don’t have direct access to the ‘parent’ page.
With these points in mind the big question should be: How do you actually use a worker and what is it useful for?
You use a worker by communicating with it using messages. All browsers support passing in a string message (Firefox 3.5 also supports passing in JSON-compatible objects). This message will be communicated to the worker (the worker can also communicate messages back to the parent page). This is the extent to which communication can occur.
The message passing is done using the postMessage API, working like this:
var worker = new Worker("worker.js"); // Watch for messages from the worker worker.onmessage = function(e){ // The message from the client: e.data }; worker.postMessage("start");
The Client:
onmessage = function(e){ if ( e.data === "start" ) { // Do some computation done() } }; function done(){ // Send back the results to the parent page postMessage("done"); }
This particular message-passing limitation is in place for a number of reasons: It keeps the child worker running securely (since it can’t, blatantly, affect a parent script) and it keeps the parent page thread-safe (having the DOM be thread safe would be a logistical nightmare for browser developers).
Right now Web Workers are implemented by Firefox 3.5 and Safari 4. They’ve also landed in the latest Chromium nightlies. Most people would balk when hearing this (only two released browsers!) but this shouldn’t be a concern. Workers allow you to take a normal piece of computation and highly parallelize it. In this way you can easily have two versions of a script (one that runs in older browsers and one that runs in a worker, if it’s available). Newer browsers will just run that much faster.
Some interesting demos have already been created that utilize this new API.
RayTracing
This demo makes use of Canvas to draw out a rendered scene. You’ll note that when you turn on the workers the scene is drawn in pieces. This is working by telling a worker to compute a slice of pixels. The worker responds with an array of colors to draw on the Canvas and the parent page changes the canvas. (Note that the worker itself doesn’t manipulate the canvas.)
Movement Tracking
(Requires Firefox 3.5. About the demo.) This one uses a number of technologies: The video element, the canvas element, and drawing video frames to a canvas. All of the motion detection it taking place in the background worker (so that the video rendering isn’t blocked).
Simulated Annealing
This demo attempts to draw outlines around a series of randomly-placed points using simulated annealing (More information). It also includes an animated PNG (works in Firefox 3.5) that continues to spin even while all the processing is occurring in the background.
Computing with JavaScript Web Workers
The other day Engine Yard started an interesting contest (which is probably over, by the time that you’re reading this).
The premise is that they would give you a phrase, which you would take the SHA1 of, and try to find another SHA1-ed string that has the smallest possible hamming distance from the original.
The phrase was posted the other day and developers have been furiously working to find a string that yields a low value.
The current leader is using a series of dedicated GPUs crunching out results at a pace of a couple hundred million per second. Considering the rate at which they’re progressing any other implementation will have a hard time catching up.
Of greater interest to me were two pure-JavaScript (1, 2) entrants into the competition – they both run completely in the browser and utilize the user’s JavaScript engine to find results. While neither of them have a prayer of overcoming the GPU-powered monsters dominating the pack, they do serve as an interesting realm for exploration.
Reading through the source to both implementations they both utilize nearly-identical tactics for computing results: They execute a batch of results broken up by a timer. I’ve played around with them in different browsers and have been able to get around 1000-1500 matches/second. Unfortunately they both peg the CPU pretty hard and even with the timer splitting they manage to bog down the user interface.
This sounds like a perfect opportunity to use Web Workers!
I took the Ray C Morgan implementation, stripped out all the UI components and timers, and pushed it in to worker (through which 4 of them are run in parallel). (I submit results back to the original implementation, just in case a good result is found.)
Check out the demo and source:
I ran the old implementation against the new one in the browsers that support Web Workers to arrive at the following results:
Browser | Old Runs/s | New Runs/s |
---|---|---|
Firefox 3.5 | 2700 | 4600 |
Safari 4 | 2500 | 8400 |
Chrome Nightly | 4500 | 9600 |
How does this implementation work? Digging in to the source of the parent launcher we can see:
// Build a worker
var worker = new Worker(“worker.js”);
// Listen for incoming messages
worker.onmessage = function(e){
var parts = e.data.split(” “);
// We’re getting the rate at which computations are done
if ( parts[0] === “rate” ) {
rates[i] = parseInt(parts[1]);
// Total the rates from all the workers
var total = 0;
for ( var j = 0; j < rates.length; j++ ) {
total += rates[j];
}
num.innerHTML = total;
// We've found a new best score, send it to the server
} else if ( parts[0] === "found" ) {
var img = document.createElement("img");
img.src = "http://www.raycmorgan.com/new-best?phrase=" +
escape(parts.slice(1).join(" "));
document.body.appendChild( img );
// A new personal best score was found
} else if ( parts[0] === "mybest" ) {
var tmp = parseInt(parts[1]);
if ( tmp < mybest ) {
mybest = tmp;
best.innerHTML = mybest;
}
}
};
// Start the worker
worker.postMessage( data.sha + " " +
data.words.join(",") + " " + data.best );[/js]
To start, we're constructing the worker and listening for any incoming messages. There are three types of messages that can come from the worker: "rate" (a 'ping' from the worker notifying the parent how quickly it's running), "found" (sent back when a new high scoring phrase has been found by the client), and "mybest" (sent when the worker gets a new personal-best high score).
Additionally we can see the initialization data sent to the client in worker.postMessage
. Unfortunately we have to pass the data in using a string in order to have it work in all browsers (only Firefox 3.5 supports the ability to pass in a raw JavaScript object).
Looking at the contents of the worker we can see some more, interesting, logic.
// … snip …
// New Personal Best Found
if (distance < myBest) {
myBest = distance;
postMessage("mybest " + myBest);
}
// New All-time Best Found
if (distance < best) {
best = distance;
postMessage("found " + phrase);
}
// ... snip ...
// Report Rate Back to Parent
function stats() {
var nowDiff = (new Date()).getTime() - startTime;
var perSec = Math.floor(processed/nowDiff*1000);
postMessage( "rate " + perSec );
}
// ... snip ...
// Get the incoming information from the parent
onmessage = function(e){
var parts = e.data.split(" ");
data = { sha: parts[0], words: parts[1].split(","), best: parts[2] };
start();
};[/js]
The two 'distance' checks take place deep in the computation logic. After a new match has been found it is compared against the existing high scores. If this a sufficiently good-enough the result is sent back to the parent page using postMessage
.
The ‘stats’ function is called periodically, which then reports back the current rate of processing to the parent page.
The ‘onmessage’ callback listens for the initialization data to come from the parent page – and once it’s been received begins processing.
—
In all I found this project to be a lot of fun – a relatively minor amount of code yielded 2-3x faster computation power. If you’re doing any computation with JavaScript you should definitely opt to use Web Workers if they’re available – the result is both faster and a better experience for the end user.
Elijah Grey (July 21, 2009 at 8:45 pm)
About “Firefox 3.5 also supports passing in JSON-compatible objects”: That’s part of the standard for Web Workers. Web Workers’
postMessage
is not the same aswindow.postMessage
.Firefox actually doesn’t support the standard correctly though when it comes with
window.postMessage
. That should also allow passing JSON, which it doesn’t.I forgot to mention; postMessage isn’t restricted to JSON. There are a few other data types not supported by JSON that can also be sent (which canvas pixel data is one of such I think).
John Resig (July 21, 2009 at 9:03 pm)
@Elijah Grey: Actually the object serialization is part of the HTML 5 specification (where postMessage is defined), not part of Web Workers. How postMessage is supposed to work is never really defined in Web Workers, but it’s pretty safe to assume that it’s intended to work very similarly to the normal postMessage method.
As far as the Firefox bug goes (with the native
window.postMessage
, not the worker postMessage) you are correct – in Firefox 3.5 it transmits a string, not an object.Nicholas C. Zakas (July 21, 2009 at 9:15 pm)
Interesting demos. I’ll be even more interested to see how Web Workers actually get used in the wild. Most of the demos I’ve seen to this point involved evaluation of video to identify parts (faces, etc.). Though cool, I’m not sure that’s a good representation of why someone would actually want to use Web Workers. Most of the proposed uses seem theoretical rather than practical, and I’m wondering if anyone has an example of using Web Workers in an every day situation to improve web application interactivity?
John Resig (July 21, 2009 at 9:21 pm)
@Nicholas: I actually had a project a number of years ago that would’ve really benefited from this technology. It was a schedule maker for RIT (the college that I attended – screenshot of it in action). It would permute out all the possible schedules and sort them based upon a variety of parameters. I had to jump through all sorts of
setTimeout
hoops to get a decent user experience. It would’ve been hugely preferable to just push that off into the background.In short: Pretty much any situation where you find yourself thinking “Hey, I need to split this task apart using timers” – that’s where Web Workers can step in.
pererinha (July 21, 2009 at 9:56 pm)
very ver interesting. it will certainly be extremely util.
Al (July 21, 2009 at 10:15 pm)
Great to see web workers in action! Looks like aprox. 200% faster in your tests, but what the *@ӣ! is going on with Safari?
Elijah Grey (July 21, 2009 at 10:21 pm)
John: I also should ask (as I don’t have a Windows or OSX machine atm) does Safari 4 support passing objects to workers?
Caleb (July 21, 2009 at 10:35 pm)
I imagine one of the most common uses would be for background polling, wouldn’t it? For instance, the polling that something like a Twitter / Facebook / Wave page would do to get real-time messages without actually making continuous full-page server requests?
Doing the polling in a background thread rather than continuously in the main thread would limit the UI impact the polling could have, right?
(I’m completely guessing here, so feel free to slap me down if I’m wildly wrong)
Boris (July 21, 2009 at 11:22 pm)
John, it’s odd that the speedup is 4x in Safari but only 2x in Firefox and Chrome… You’re using 4 workers, right? How many CPU cores? Bug filed on the speedup not being 4x?
Dannii (July 21, 2009 at 11:25 pm)
How hard would it be to make a Worker wrapper, so that the same worker script could be run with either a worker thread or a timer/callbacks in old browsers? For this to really be usable now we need to be able to easily use either.
stoimen (July 22, 2009 at 2:07 am)
It really sounds great but with all that browser compatibility all the new coolest things in js hit the world with years delay :(
Chris Anderson (July 22, 2009 at 2:50 am)
Reading this article I kept thinking that Web Workers remind me of Erlang. I’m not thinking so much about the concurrency (though that’s nice) but rather the isolation between processes. I’ve found message passing to much more robust than shared memory.
I wonder if the ability to pass raw data (like canvas pixels or JSON objects) instead of strings as the message contents can end up causing problems. A good implementation would keep the web worker from modifying an object after posting it. A bad implementation could screw this up and lose all the benefits of isolation and message passing.
Anders Rune Jensen (July 22, 2009 at 3:51 am)
Interesting article. I like the approach to doing parallelism with sending messages. It’s functional like it should be.
I tried the ray tracing demo in Firefox on 3.5.1 (Windows 2003) and it crashed the browser. I also tried the web worker demo you provided on Chrome 3.0.194.3 (Ubuntu 9.04) and it crashed the tab. Of course that is almost to be expected with the freshness of these tests :) A very interesting future is a head for Javascript, especially for doing UI.
Peter (July 22, 2009 at 6:37 am)
I tried the raytracing demo (Fx 3.5.1, WinXP x64) and had no crashes.
But the results weren’t really that impressive: Time differences between runs is greater than the differences produced by using none, 1, 4, 8 or 16 workers.
Maarx (July 22, 2009 at 7:16 am)
High lvl Js, you rox guy !
John Resig (July 22, 2009 at 8:56 am)
@Elijah Grey: In my limited testing, Safari 4 does not appear to support object passing with worker postMessage. I didn’t test Chrome, so it may support it.
@Caleb: Background polling is a viable use case, absolutely. Although the polling itself isn’t so much the desired use of the worker – it’s the processing that needs to occur after the polling occurs. (e.g. Converting JSON into HTML).
@Boris: Yeah, this is with 4 workers – I’m not sure why the speed-up didn’t match in Firefox 3.5. I’ll see if I can find a pattern and file a bug on it.
@Dannii: I thought about this – but it’ll be tricky. You would have to make your processing code use setTimeout/setInterval from the start (this code would end up working in both a worker and on a normal web site). So while the result would be slightly slower for worker-enabled browsers at least it would work in both cases.
@Chris Anderson: I’d doubt (knock on wood) that the browsers would mess up functionality like that. Since there are a bunch of shiny demos that could benefit from the behavior it’s doubtful that they would want to implement it poorly.
@Peter: I’ve had varying results with Firefox on that demo. By the admission of Oliver Hunt (the creator of the demo) it’s heavily optimized to work in Safari/WebKit – so keep that in mind.
Tom Palmer (July 22, 2009 at 9:35 am)
So, I’ve often wondered why a separate js file is required instead of a function pointer. Is it just to enforce the lack of shared memory? If so, I guess that makes sense, but requiring a different file still seems quite awkward.
John Seymore (July 22, 2009 at 9:40 am)
http://www.yafla.com/dforbes/resources/moonbat/moonbat-driver.html
Surprised you didn’t mention that, given that it was one of the earliest, most interesting examples of web workers.
John Seymore (July 22, 2009 at 9:47 am)
Sorry I should have linked to the actual description of what that is
http://www.yafla.com/dforbes/Web_Workers_and_You__A_Faster_More_Powerful_JavaScript_World/
It was useful for me.
Anyways, thanks for the great post, John.
John Resig (July 22, 2009 at 9:54 am)
@Tom Palmer: This is due to the fact that a function could have a closure to a non-threadsafe object (like a DOM element) and that would cause a lot of problems. By forcing everything to be contained in an external script no such problems will arise.
@John Seymore: Interesting benchmark, thanks!
Brian Reindel (July 22, 2009 at 11:36 am)
Hi John,
Great job describing Workers, and how they might be used in the wild. I just wanted to be clear, though, after reading your post and browsing the spec. You use the word “parallel” a few times, and I just wanted to be sure on your meaning. Several worker constructors can be initialized, running in the background in parallel, but you can’t setup a concurrent scenario, where they begin processing the same task simultaneously — is that correct? We’re still talking a procedural process in the strictest sense?
Also, it looks like you can delegate to a single worker from several views, updating a worker process while in progress. However, I have a hard time determining what constitutes a view with regard to a worker’s “state”. It looks like from the spec if you begin a new worker process, it runs only for that page. You can’t access that same worker later in the application after you’ve clicked a link to visit another page. Is that correct as well?
Thanks again for all you hard work and research!
V1 (July 22, 2009 at 1:55 pm)
One thing i dislike about the workers is that it requires a “extra” file to be loaded in the worker. It would be much handier if we could just assign functions to worker
e.g.
var worker = new Worker(fn,fn,fn,fn);
worker.push(fn);
But i guess i just got to life with that.
John Resig (July 22, 2009 at 4:08 pm)
@Brian Reindel: You’re correct – parallel in the sense that they’re both running simultaneously but not synchronized (one worker may go faster than the other, for example).
And yes, once you’ve left a page the worker is dead (unless you’re running the worker in a parent page and the user is navigating in an iframe, for example).
@V1: See my comments to Tom Palmer, above.
Rob (July 22, 2009 at 4:24 pm)
I wrote a driving directions page about a year ago that could have benefitted from this. Was using Microsoft Virtual Earth maps and needed to present the user with an optimial route through all their stops. I had a nice little algorithm to do this, but it really bogged down if you had more then about 10 stops. Ended up having to use an alternative algorithm when stops > 10, which gave an almost optimal route.
Erik Reppen (July 22, 2009 at 4:43 pm)
Now all we have to do is make enough really neat and wildly popular stuff that’s not even possible in IE for free that the old dogs are finally shot and replaced with a new one that vaguely resembles spec-compliance. Being able to do both and just let the old browsers be slower sounds reasonable but that assumption forgets that managers rarely are.
Tim McCormack (July 22, 2009 at 9:27 pm)
The really cool part of this is when you put a couple of these workers in all the pages of your website, and let them talk to a non-sandboxed script that can talk to the server. Free cloud computing, at your users’ expense!
Heck, there’s already Java->Javascript compilers (look at GWT, for instance), so people could write their code in Java and have it farmed out.
Mariusz Nowak (July 23, 2009 at 6:14 am)
What about passing canvas pixel data to calculate its histogram. Can it be done sensibly with web workers ?
Roshan Shrestha (July 23, 2009 at 9:19 am)
“…and it keeps the parent page thread-safe (having the DOM be thread safe would be a logistical nightmare for browser developers).”
You mean “NOT having the DOM be thread safe”?
Ray Morgan (July 23, 2009 at 11:59 am)
This is very cool stuff. I was going to write another version to use Web Workers (once I remembered about them) but sadly didn’t have time to do it. We were also running C/Java/Erlang implementations (to see the differences between them), so I spent a good amount of time on those.
As a side note.. we were able to process over at least 10 billion SHA distances via the Javascript implementation alone.
Thanks for the link and using our code.
Dave Savage (July 24, 2009 at 3:05 pm)
Great article John! I actually have a project coming up that I am going to try and use this for.
-Dave
V1 (July 27, 2009 at 7:46 am)
Thanks for the clarification, but i still think this is a huge miss. I got around this by using a serverside file that echos back the contents i send to it.
eg.
var work = new Worker(“createworker.php?include=worker.js&functionname=” functionname.toString() + “&funcionname2” + functionname2.toString());
gary johnson (August 3, 2009 at 11:57 pm)
My first experiment was to try and access internals in workers.
var mydir = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
Error: Components is not defined
Source File: chrome://gj53run/content/utilsxul.js
Line: 195
Would I be correct in assuming that workers will not let you do this sort of thing?
Jack (August 4, 2009 at 7:30 pm)
Great article, i really feel like the big advances to the ‘bottom line’ of user experience will come in the next few years with web workers.
On a related note, any consideration on adding web workers to more computationally intense aspects of jQuery–for example the computational aspects of animations/effects?
Or perhaps just functionality for using web workers in the jQuery $ namespace. Either way i’m sure it’d do tons to increase adoption.
Mario (September 17, 2009 at 7:04 pm)
Could web workers optimize this emulator:
http://benfirshman.com/projects/jsnes/
Adam (September 27, 2009 at 7:50 am)
@ Mariusz Nowak, and anyone who tried to manipulate canvas pixels in workers
I’ve tried to pass canvas pixel data, and it works in firefox. In webkit, you have to do the hard work of converting to strings and back yourself, and the CanvasPixelArray object is immutable( you can only change the values, not the array itself). CanvasPixelArray behaves just like an Array in firefox and the ability to pass JSON objects makes it a lot easier.
Arnold47 (October 22, 2009 at 4:47 pm)
I know the west is going to have water problems. ,
Jacob Nelson (November 20, 2009 at 1:02 pm)
Web Workers is a really good feature.
I tried with a small example, which displays the prime numbers.
I am going to do some research on this and will write on by technical blog.
Richard Revis (December 10, 2009 at 4:53 pm)
@Tim McCormack
I had the same idea about Cloud Computing and built a dispatcher that sends jobs to your web site visitors for processing:
http://theplanis.com/clouds/
(Includes a link to the proof of concept code on git hub.)
Paris Hotell (December 18, 2009 at 12:22 pm)
I have a feeling that server-side js will eventually become a major player in the web world, especially if solid, reliable implementations are created. The idea of having a uniform language to communicate between the server and client with a decent data transportation format (JSON) has been something I’ve long for, enough so that one of my New Year’s resolutions was to start contributing to a SS-JS implementation this year.
luis maldonado (December 21, 2009 at 5:46 pm)
Cool stuff! are web workers supported on safari mobile, and if so how do I get started…
great job thanks…
Sherwin Shine (January 27, 2010 at 2:32 pm)
Thanks John for sharing this information. I came to know about web workers feature and i will try with next project.
Philip Mein (February 11, 2010 at 9:50 am)
I am using web workers and also need to access internals in workers. Did anyone find a workaround to accessing Components.classes?
Error: Components is not defined
thanks
not sure if this will email me on replies so if you have a solution an email to me would be greatly appreciated.
Ryan O'Rorke (May 13, 2010 at 4:52 am)
Hey,Very interesting blog, Please visit my website at http://www.canvaspixel.com ,we supply a large range of modern contemporary images on canvas, or you can simply upload your own image, Unbeatable prices and we will not be beaten in terms of quality, Delivery times, or customer service, Thanks
a (August 13, 2010 at 7:57 am)
asdsad
a (August 13, 2010 at 7:57 am)
dssssssssssssssdfffffffff
Shidhin (August 20, 2010 at 3:42 am)
I wonder if Web worker can be used for a grid computing kind of stuff. To solve a major problem which requires highly efficient server and resource can be given to the web workers. So it should run in the client side and post to the server.
Since it’s a background work, it won’t make any nuisance to the user.
I think that’s the best usage of web worker