Source Code

See this code in action: Firefox 3.5 Web Workers Test v1.3
Download: firefox-webworkers-v1.3.js (save as)
gist: http://gist.github.com/212380
/**
 * Firefox Web Workers Example v1.3
 *
 * http://pmav.eu/stuff/firefox-webworkers
 *
 * This example uses N Web Works for generating LCG pseudorandom numbers, each Worker starts a new stream.
 *
 * pmav, 2009
 */
(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(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 workerId = event.data.workerId;
        var payload = event.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(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/firefox-webworkers.js", // Web Worker script


    /**
     * WORKER.run()
     *
     * Web Worker main code (thread dies after the execution of this code)
     */
    run : function() {
      onmessage = function(event) {
        var workerId = event.data.workerId;
        var payload = event.data.payload;
        var n, i;

        var data = {};
        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(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; 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(); } }());