HTML5 Zone is brought to you in partnership with:

Jos works as Architect for JPoint. In the last couple of years Jos has worked on large projects in the public and private sector. Ranging from very technology focusses integration projects to SOA/BPM projects using WS-* and REST based architectures. Jos has given many presentations on conferences such as Javaone, NL-JUG, Devoxx etc., and has written two books for Manning: Open Source ESBs in Action and (published in the next couple of months) SOA Governance in Action. In this last book Jos shows how, with some good practical governance approaches, you can create great WS-* and REST based services and APIs. Besides this he has his own blog where he writes about interesting technologies and shares his ideas about REST, API Design, Scala, Play and more. Jos is a DZone MVB and is not an employee of DZone and has posted 51 posts at DZone. You can read more from them at their website. View Full User Profile

Easily parallelize jobs using web workers and a threadpool with HTML5

05.20.2012
| 4074 views |
  • submit to reddit

I've been experimenting with web workers and the various browser implementations. Most of the articles I've seen show an example where a single worker thread is started in the background to execute some heavy task. This frees up the main thread to render the rest of the webpage and respons to user input. In a previous article I showed how you can off-load CPU heavy tasks to a seperate web worker thread. In that example we used a couple of libraries to get the following effect:

sophie web worker

Sinc almost everyone nowadays has multiple cores it's a waste not to use them. In this article I'll show how we can use a simple threadpool to parallelize this even further and increase the rendering time by +/- 300%. You can run this example from the following location: http://www.smartjava.org/examples/webworkers2/

The threadpool code

To test multiple threads with web workers I wrote a simple (and very naive) threadpool / taskqueue. You can configure the maximum number of concurrent web workers when you create this pool, and any 'task' you submit will be executed using one of the available threads from the pool. Note that we aren't really pooling threads, we're just using this pool to control the number of concurrently executing web workers.

function Pool(size) {
    var _this = this;
 
    // set some defaults
    this.taskQueue = [];
    this.workerQueue = [];
    this.poolSize = size;
 
    this.addWorkerTask = function(workerTask) {
        if (_this.workerQueue.length > 0) {
            // get the worker from the front of the queue
            var workerThread = _this.workerQueue.shift();
            workerThread.run(workerTask);
        } else {
            // no free workers,
            _this.taskQueue.push(workerTask);
        }
    }
 
    this.init = function() {
        // create 'size' number of worker threads
        for (var i = 0 ; i < size ; i++) {
            _this.workerQueue.push(new WorkerThread(_this));
        }
    }
 
    this.freeWorkerThread = function(workerThread) {
        if (_this.taskQueue.length > 0) {
            // don't put back in queue, but execute next task
            var workerTask = _this.taskQueue.shift();
            workerThread.run(workerTask);
        } else {
            _this.taskQueue.push(workerThread);
        }
    }
}
 
// runner work tasks in the pool
function WorkerThread(parentPool) {
 
    var _this = this;
 
    this.parentPool = parentPool;
    this.workerTask = {};
 
    this.run = function(workerTask) {
        this.workerTask = workerTask;
        // create a new web worker
        if (this.workerTask.script!= null) {
            var worker = new Worker(workerTask.script);
            worker.addEventListener('message', dummyCallback, false);
            worker.postMessage(workerTask.startMessage);
        }
    }
 
    // for now assume we only get a single callback from a worker
    // which also indicates the end of this worker.
    function dummyCallback(event) {
        // pass to original callback
        _this.workerTask.callback(event);
 
        // we should use a seperate thread to add the worker
        _this.parentPool.freeWorkerThread(_this);
    }
 
}
 
// task to run
function WorkerTask(script, callback, msg) {
 
    this.script = script;
    this.callback = callback;
    this.startMessage = msg;
};

Using the threadpool

To use this threadpool we now just have to do this:

    var pool = new Pool(6);
    pool.init();

This will create a pool that will allow a maximum number of 8 threads running concurrently. If we want to create a task to be executed by this pool we just create a workerTask and submit it like this:

      var workerTask = new WorkerTask('extractMainColor.js',callback,wp);
      pool.addWorkerTask(workerTask);

This will create a web worker from 'extractMainColor.js' and register the supplied function as callback. Once the worker is ready to be run, the last argument will be used to send a message to the worker. A caveat on this implementation. I now assume that the when the web worker sends a message back it will close itself after sending this message. As you can see in the following example:

importScripts('quantize.js' , 'color-thief.js');
 
self.onmessage = function(event) {
    var wp = event.data;
    var foundColor = createPaletteFromCanvas(wp.data,wp.pixelCount, wp.colors);
    wp.result = foundColor;
    self.postMessage(wp);
 
    // close this worker
    self.close();
};

Results

I've tested this a couple of times with different settings for number of concurrent threads. The results are shown in the following table:

Chrome:

Number of threads Total rendering time
1 14213
2 9956
3 8778
4 7846
5 6924
6 6309
7 5912
8 5468
9 5201
10 5193
11 5133
12 5208

The result for firefox are less impressive, but you can still see a big gain:

Firefox:

Number of threads Total rendering time
1 17909
2 11273
3 10422
4 10154
5 10115
6 10052
7 10000
8 9997

As you can see, both for Firefox and Chrome it's useful to not just use a single web worker, but further split the tasks. For firefox we can see a big gain if we use two web workers, and for chrome we keep on getting better results to 8 or 9 parallel web workers!

Published at DZone with permission of Jos Dirksen, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)