Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

HTML5 Web Workers: Classic Message Passing Concurrency

DZone's Guide to

HTML5 Web Workers: Classic Message Passing Concurrency

· Web Dev Zone
Free Resource

Should you build your own web experimentation solution? Download this whitepaper by Optimizely to find out.

Most concurrency frameworks I write about on this blog consist of numerous layers of abstraction. Consider the Task Parallel Library, for instance: it’s a wrapper on top of the .NET Thread Pool, which is a wrapper on top of Windows threads. This cruft of low-level abstraction layers forces certain expectations from the newer libraries – namely, they must allow direct access to shared state, provide synchronization mechanisms, volatile variables, atomic synchronization primitives, …

It seems that JavaScript (HTML5) with its Web Workers standard enjoys the lack of abstraction cruft for threading in the JavaScript world. Because there are no underlying low-level libraries for multithreaded JavaScript computation (in the browser), Web Workers are free to reinvent not only the APIs, but also the concurrency style.

Web Workers provide a message-passing model, where scripts can communicate only through well-defined immutable messages, do not share any data, and do not use synchronization mechanisms for signaling or data integrity. Indeed, Web Workers are not ridden with classic concurrency problems such as deadlocks and race conditions – simply because these concurrency problems are impossible.

Frankly, I’m a little jealous of JavaScript developers who can now leverage multithreading in their browser-side applications. The Web Workers API is minimalistic but done right. Below is a small example of a multithreaded prime number search with Web Workers – if you are looking for a more detailed introduction and walkthrough, check out the following resources:

First, the UI:

<input type="text" id="range_start"
       placeholder="start (e.g. 2)" /><br/>
<input type="text" id="range_end"
       placeholder="end (e.g. 100000)" /><br/>
<label for="dop">Degree of parallelism:</label>
<input type="range" id="dop"
       min="1" max="8" value="4" step="1" /><br/>
<input type="button" id="calculate" value="Calculate" />

image

Now the actual business. When the “Calculate” button is clicked, we spawn the specified number of worker threads to do the calculation in the background. The main thread passes to the workers the range of primes they will work on, and receives from the workers progress reports and the final count:

//Some parts of the code elided for clarity
$(document).ready(function () {
    $("#calculate").click(function (e) {
        e.preventDefault();
        var rangeStart = parseInt($("#range_start").val());
        var rangeEnd = parseInt($("#range_end").val());
        var parallelism = parseInt($("#dop").val());
        createWorkers(parallelism, rangeStart, rangeEnd);
    });
});
function createWorkers(parallelism, start, end) {
    var range = end - start;
    var chunk = range / parallelism;
    var count = 0;
    var done = 0;
    for (var i = 0; i < parallelism; ++i) {
        var worker = new Worker("prime_finder.js");
        worker.onmessage = function(event) {
            if (event.data.type === 'DONE') {
                ++done;
                count += event.data.count;
                if (done == parallelism) ...
            } else if (event.data.type === 'PROGRESS') {
                var progress = event.data.value;
                ...
            }
        };
        var init = {
            start: start + i*chunk,
            end: start + (i+1)*chunk,
            idx: i
        };
        worker.postMessage(init);
    }
}

Note the communication between the threads. The main thread uses Worker.postMessage to provide data to the worker thread, and receives from it status updates using the onmessage event. The worker thread runs the prime_finder.js script:

//The isPrime function elided for brevity
self.onmessage = function (event) {
    var start = event.data.start;
    var end = event.data.end;
    var size = end - start;
    var count = 0;
    for (var i = start; i < end; ++i) {
        if (isPrime(i)) ++count;
        if (i % 1000 === 0) {
            self.postMessage({
                type: 'PROGRESS',
                value: 100.0*((i-start)/size),
                idx: event.data.idx
            });
        }
    }
    self.postMessage({
        type: 'DONE',
        count: count,
        idx: event.data.idx
    });
};

Here we see the opposite direction – the worker thread periodically posts progress reports and eventually reports completion. The whole thing is triggered by the receipt of the initial message from the main thread.

image
(In this screenshot you can see how uneven the distribution of work turns out to be – the first thread finishes very quickly while the fourth thread lags behind quite slowly…)

To experiment with this code, you can download the full demo, including a very small Node.js server that serves it.

If you have a C# or C++ development background, it all probably feels very unnatural to you. Where is the shared state? Where are the synchronization mechanisms? Where are the function pointers? – Indeed, it can be scary to write a multithreaded program using only asynchronous message passing – but it’s a much cleaner start than many of the libraries we have today.

 

Source: http://blogs.microsoft.co.il/blogs/sasha/archive/2012/02/05/html5-web-workers-classic-message-passing-concurrency.aspx

Implementing an Experimentation Solution: Choosing whether to build or buy?

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}