Tipps und Tricks zu den Themen: Software- und Webentwicklung

Dienstag, 16. April 2013

Parallelisierung mittels Threads in JavaScript (Polling)

Ein Thread (Prozessfaden) ist ein wichtiger Bestandteil höherer Programmiersprachen. Der Nutzen von Threads besteht darin, dass parallel verschiedene Prozessfäden gleichzeitig ausgeführt werden können. Sehr häufig kommt dabei das Polling-Prinzip (zyklische Abfrage) zum Einsatz. Das heißt ein Objekt bleibt solange automatisiert, bis der Prozess explizit beendet wird. Vor allem bei der Entwicklung von Spielen sind solche Threads sehr wichtig.

Leider bietet JavaScript von Hause aus keine Browser-übergreifende Technik um Threads zu erzeugen, die einen eigenen Prozessfaden vom Browser oder Betriebssystem zugewiesen bekommen. Jedoch lässt sich der Haupt-Thread, den der Browser für jede angezeigte Seite zur Verfügung stellt, beliebig aufteilen (Pseudo-Threads). Wir haben eine JavaScript-Klasse "Thread" erstellt, die von der Anwendung dem Java-Pendant ähnelt und etwas erweitert.

1. Die JavaScript-Klasse "Thread"
function Thread(CLASS) {

    /* PRIVATE MEMBERS */

    var EXECUTE = false;
    var ACTIVE = false;
    var DATA = [];
    var INTERVAL = 15; // set time slices in milliseconds
    var PROCESS = null;
    var ID = null;
    var WIN = window;

    /* THREAD CONSTRUCTOR */

    (function () {

        ID = WIN.THREAD_COUNT = (WIN.THREAD_COUNT || 0) + 1;

        WIN.THREAD_ARRAY = WIN.THREAD_ARRAY || [];
        WIN.THREAD_ARRAY.push(ID);
        WIN.THREAD_INDEX = WIN.THREAD_INDEX || 0;

    })();

    var threadHandler = function () {

        /* SWITCH ACTIVE THREAD */

        if(EXECUTE && WIN.THREAD_ARRAY[WIN.THREAD_INDEX] == ID) {

            if(!ACTIVE) processHandler();
            else if(++WIN.THREAD_INDEX >= WIN.THREAD_ARRAY.length)
                 WIN.THREAD_INDEX = 0;
        }
    }

    var processHandler = function () {

        /* PROCESS ACTIVE THREAD */

        if(EXECUTE && WIN.THREAD_ARRAY[WIN.THREAD_INDEX] == ID) {

            ACTIVE = true;
            //console.log("START [Thread-Nr: " + ID + "]");
            if(DATA.length) DATA.pop()(); else CLASS.run();
            //console.log("ENDE  [Thread-Nr: " + ID + "]");
            WIN.setTimeout(processHandler, 1);

        } else ACTIVE = false;
    };

    /* PUBLIC FUNCTIONS */

    this.stack = function (func) {

        DATA.push(func);
    };

    this.queue = function (func) {

        DATA.unshift(func);
    };

    this.clear = function () {

        DATA = [];
    };

    this.start = function () {

        EXECUTE = true;
        PROCESS = WIN.setInterval(threadHandler, INTERVAL);
    };

    this.cancel = function () {

        EXECUTE = false;
        WIN.clearInterval(PROCESS);
    };

    this.pause = function (value) {

        EXECUTE = !value;
    };
}
Mit .stack() und .queue() lässt sich zusätzlicher Code direkt an einen Thread zuweisen. Mit .start(), .cancel(), .pause() und .clear() lässt sich der Thread von außen steuern.


2. Beispiel: Implementierung der Klasse "Thread"
(function () {

    function Class_1() { // * implements run:  

        this.run = function () {

            /* PLACE YOUR CODE HERE: */

            for(var i = 0; i < 5000; i++)
                var dummy = (Math.pow(3*Math.PI, 2)*25*1.5*1.5*1.5*1.5);
        };
    }

    function Class_2() { // * implements run:  

        this.run = function () {

            /* PLACE YOUR CODE HERE: */

            for(var i = 0; i < 50000; i++)
                var dummy = (Math.pow(3*Math.PI, 2)*25*1.5*1.5*1.5*1.5);
        };
    }

    function Class_3() { // * implements run:  

        this.run = function () {

            /* PLACE YOUR CODE HERE: */

            for(var i = 0; i < 500000; i++)
                var dummy = (Math.pow(3*Math.PI, 2)*25*1.5*1.5*1.5*1.5);
        };
    }

    /* CREATE OBJECTS AND START THREADS */

    var myClass_1 = new Class_1();
    var myThread_1 = new Thread(myClass_1);
    myThread_1.start();

    var myClass_2 = new Class_2();
    var myThread_2 = new Thread(myClass_2);
    myThread_2.start();

    var myClass_3 = new Class_3();
    var myThread_3 = new Thread(myClass_3);
    myThread_3.start();

    /* DEMO: put some additional code to the stack */

    myThread_1.stack(function () {

        for(var i = 0; i < 5000; i++)
            var dummy = (Math.pow(3*Math.PI, 2)*25*1.5*1.5*1.5*1.5);
    });

    myThread_2.stack(function () {

        for(var i = 0; i < 50000; i++)
            var dummy = (Math.pow(3*Math.PI, 2)*25*1.5*1.5*1.5*1.5);
    });

    myThread_3.stack(function () {

        for(var i = 0; i < 500000; i++)
            var dummy = (Math.pow(3*Math.PI, 2)*25*1.5*1.5*1.5*1.5);
    });

})();
Als Beispiel dienen 3 Threads mit jeweils unterschiedlich langem Berechnungsaufwand innerhalb der ".run()"-Funktion. Das Ergebnis ist: Thread_1.run() wird wesentlich häufiger hintereinander ausgeführt als Thread_2.run() und Thread_2.run() wesentlich häufiger als Thread_3.run().

Nachdem 250 mal .run() ausgeführt wurde:

Thread_1: 169 mal
Thread_2: 61 mal
Thread_3: 20 mal

Inwieweit diese Threads letztendlich von Mehrkernprozessoren profitieren liegt allein am Browser, denn er muss die zeitlich gesteuerten Funktionen auf verschiedene eigene Threads verteilen und die Ergebnisse synchronisieren.

3. Blick in die Zukunft: Echte Multicore Parallelisierung (HTML5)

Viele JavaScript-Bibliotheken, die im Web kursieren und echte Parallelisierung versprechen, basieren auf dasselbe Prinzip wie die Thread-Klasse oberhalb. Allerdings neu in HTML5 und vom W3C offiziell begleitet ist eine native Unterstützung bei der Erzeugung echter Threads:

http://www.w3.org/TR/workers/
(function () {

    var worker = new Worker('mycode.js');

    worker.onmessage = function(event) {

        // process the result of event.data
    };

})();
Solche Threads werden auch auf mehrere Kerne eines Prozessors verteilt, die dann auf Browser-Ebene tatsächlich parallel ausgeführt werden.



Keine Kommentare:

Kommentar veröffentlichen