Source Code

See this code in action: Javascript Web Workers Test v1.3
Download: javascript-webworkers-v1.3.1.js (save as)
gist: http://gist.github.com/212380
/**
 * Firefox Web Workers Example v1.3.1
 *
 * http://pmav.eu/stuff/javascript-webworkers
 *
 * This example uses N Web Workers for generating LCG pseudorandom numbers, each Worker starts a new stream.
 *
 * pmav, 2009-2010
 */
(function () {

    MAIN = {

        workersNumber : 0,  // Total number of Web Workers (dynamic)
        workers: [],        // Web Workers reference
        workersEnded : 0,   // Number of terminated Web Workers

        lcgNumber : 0,      // # of LCG numbers to generate (dynamic)
        lcgNumberPower : 0, // LCG numbers in powers of 2

        startTime : null,   // Current test start time
        results : null,     // Current test results (for debugging)
        testNumber : 1,     // Current test number

        benchmark : {       // Benchmark stuff

            mode : false,
            currentTest : 0,
            currentRun: 0,
            runResults : [],
            finalResults : [],

            tests : [
            {
                workers: 1,
                size: 27, // 2^27
                runs: 10
            },

            {
                workers: 2,
                size: 27,
                runs: 10
            },

            {
                workers: 4,
                size: 27,
                runs: 10
            },

            {
                workers: 8,
                size: 27,
                runs: 10
            }
            ]
        },


        /**
         * MAIN.run()
         *
         * Main code function (this will run only once per test)
         */
        run : function(workersNumber, lcgNumberPower) {
            var i, t, interval, lcgNumberSeed;
            this.workersEnded = 0;
            this.workers = [];
            this.results = [];

            try {

                // Get test values
                if (typeof workersNumber === "undefined") {
                    this.workersNumber = parseInt(document.getElementById("webWorkersNumber").innerHTML, 10);
                    t = parseInt(document.getElementById("lcgNumber").innerHTML, 10);
                    this.lcgNumber = Math.pow(2, t);
                    this.lcgNumberPower = t;
                } else {
                    this.workersNumber = workersNumber;
                    this.lcgNumber =  Math.pow(2, lcgNumberPower);
                    this.lcgNumberPower = lcgNumberPower;
                }

                interval = Math.ceil(this.lcgNumber / this.workersNumber);

                this.startTime = new Date();

                // Setup Workers
                for (i = 0; i < this.workersNumber; i++) {
                    this.workers[i] = new Worker(WORKER.scriptFilename);
                    this.workers[i].onmessage = this.callback;
                    this.workers[i].onerror = this.error;
                }

                // Start workers
                for (i = 0; i < this.workersNumber; i++) {
                    lcgNumberSeed = 1 + (interval * i);

                    if (lcgNumberSeed + interval > this.lcgNumber) {
                        interval = this.lcgNumber - (interval * i);
                    }

                    this.sendMessage(i, lcgNumberSeed, interval);
                }

            } catch (e) {
                alert(e);
            }
        },


        /**
         * MAIN.benchmark();
         *
         * Run several tests and output some useful info
         */
        runBenchmark : function () {

            if (this.benchmark.currentTest == 0) {

                // Benchmark Start
                this.benchmark.mode = true;
            }

            if (this.benchmark.currentRun < this.benchmark.tests[this.benchmark.currentTest].runs) {

                // Still have some runs to do
                this.run(this.benchmark.tests[this.benchmark.currentTest].workers, this.benchmark.tests[this.benchmark.currentTest].size);
                this.benchmark.currentRun++;

            } else {

                // Next test?
                this.benchmark.finalResults.push(UTIL.average(this.benchmark.runResults));
                this.benchmark.currentTest++;

                // Reset Run
                this.benchmark.currentRun = 0;
                this.benchmark.runResults = [];

                if (this.benchmark.currentTest < this.benchmark.tests.length) {

                    // Next test!
                    this.run(this.benchmark.tests[this.benchmark.currentTest].workers, this.benchmark.tests[this.benchmark.currentTest].size);
                    this.benchmark.currentRun++;

                } else {

                    // Benchmark end
                    UTIL.log("");

                    var i, s;
                    for (i = 0; i < this.benchmark.finalResults.length; i++) {

                        if (i == 0) {
                            s = "Standart Test, 100%";
                        } else {
                            s = Math.round(((this.benchmark.finalResults[i] / this.benchmark.finalResults[0]) * 100)) + "%";
                        }

                        UTIL.log("Benchmark Group #"+(i+1)+" average: "+this.benchmark.finalResults[i]+" ms, "+this.benchmark.tests[i].workers+ " Worker" + ( this.benchmark.tests[i].workers === 1 ? "" : "s" ) + " [" + s + "]");
                    }

                    // Reset Test
                    this.benchmark.currentTest = 0;
                    this.benchmark.currentRun = 0;
                    this.benchmark.finalResults = [];

                    JQ.callback();
                }
            }
        },


        /**
         * MAIN.sendMessage(int, int, int)
         *
         * Send a message to a Web Woker with id workerId
         *
         * workerId - Web Worker unique ID
         * lcgNumberSeed - First LCG number (stream seed)
         * iterations - # of LCG numbers that each thread will generate
         */
        sendMessage : function(workerId, lcgNumberSeed, iterations) {
            try {
                var data = {};
                data.workerId = workerId;
                data.payload = {};
                data.payload.lcgNumberSeed = lcgNumberSeed;
                data.payload.iterations = iterations;

                this.workers[workerId].postMessage(JSON.stringify(data));

            } catch (e) {
                throw e;
            }
        },


        /**
         * MAIN.callback(object)
         *
         * Callback entry point (no context), this function executes in a single thread environment
         */
        callback : function(event) {
            try {
                var data = JSON.parse(event.data);

                var workerId = data.workerId;
                var payload = data.payload;

                MAIN.workers[workerId].terminate();
                MAIN.workersEnded += 1;

                MAIN.results[workerId] = payload.lastNumber;

                if (MAIN.workersEnded === MAIN.workersNumber) { // All Web Workers are done
                    var time = ((new Date) - MAIN.startTime);

                    // Output run results
                    UTIL.log(MAIN.testNumber + ") " + MAIN.workersNumber + " Worker" + ( MAIN.workersNumber === 1 ? "" : "s" ) + ", Test Size: 2^"+MAIN.lcgNumberPower+" - "+ time + " ms");
                    MAIN.testNumber += 1;

                    if (MAIN.benchmark.mode) {
                        MAIN.benchmark.runResults.push(time);
                        MAIN.runBenchmark();
                    } else {
                        JQ.callback();
                    }

                }

            } catch (e) {
                alert("a: "+e);
            }
        },


        /**
         * MAIN.error(object)
         *
         * Handles worker errors (in a amazing way...)
         */
        error : function(error) {
            alert("Worker error: " + error.message);
        }

    }


    /**
     * WORKER
     *
     * Web Work related code
     */
    WORKER = {

        scriptFilename : "assets/js/javascript-webworkers-v1.3.1.js", // Web Worker script


        /**
         * WORKER.run()
         *
         * Web Worker main code (thread dies after the execution of this code)
         */
        run : function() {
            onmessage = function(event) {
                var data = JSON.parse(event.data);

                var workerId = data.workerId;
                var payload = data.payload;
                var n, i;

                data.workerId = workerId;
                data.payload = {};

                n = payload.lcgNumberSeed;

                for (i = 0; i <= payload.iterations; i += 1) {
                    n = UTIL.linearCongruentialGenerator(n);
                }

                data.payload.lastNumber = n; // Only save the last number in the stream

                postMessage(JSON.stringify(data)); // Callback to MAIN.callback()

                return;
            };
        },


        /**
         * WORKER.isWorker()
         *
         * Check if active thread is a Web Worker
         */
        isWorker : function() {
            if (typeof document === "undefined") {
                return true;
            } else {
                return false;
            }
        }

    };


    /**
     * UTIL
     *
     * Auxiliar code for this example
     */
    UTIL = {

        a : 1103515245, // LCG a (glibc value)
        c : 12345,      // LCG c (glibc value)
        m : 4294967296, // LCG M (glibc value)

        /**
         * linearCongruentialGenerator(int)
         *
         * Linear congruential generator: X(n+1) = ((a * X(n)) + c) % M
         * More information: http://en.wikipedia.org/wiki/Linear_congruential_generator
         */
        linearCongruentialGenerator : function(n) {
            return ((this.a * n) + this.c) % this.m;
        },


        logDom : null,

        log : function(loggingText) {
            if (this.logDom === null) {
                this.logDom = document.getElementById("log");
            }

            this.logDom.innerHTML = loggingText + "
" + this.logDom.innerHTML; }, average : function(values) { var i, sum = 0.0; for (i = 0; i < values.length; i++) { sum += values[i]; } return Math.floor(sum / values.length); } } // Verifies if is a Web Worker thread that is executing at the moment if (WORKER.isWorker()) { WORKER.run(); } }());