Ich hab mir mal einen allgemeinen und für alle Zwecke geeigneten WebWorkerPool. Man kann die Anzahl der Worker skalieren, was auch bei den Test sich spürbar auswirkte. Auch eine ähnliche Weise sind nun auch die WebWorker auf MP4toGIF.com implementiert, was auch da alles spürbar beschleunigt hat.
Beim eigentlichen WebWorker muss man nur das Antwort-Format einhalten:
{
result:{...},
index:0
}
Index ist der Index des Workloads und wurde in event.data.index übergeben wären der eigentliche Workload in event.data.workload zu finden
ist.
function WebWorkerPool(){
this.workerScript = "";
this.workerData = []; // {msg:{},callback:function}
this.callback = null; //global callback for workerData
this.finishCallback = null;
this.finishCounter=0;
this.worker = null;
this.workers = [];
this.callbackWrapper=function(controller){
return function(event){
controller.finishCounter--;
if(event.data.index && controller.workerData[event.data.index].callback){
controller.workerData[event.data.index].callback(event.data.result);
}
else if(controller.callback){
controller.callback(event.data.result);
}
if(controller.finishCounter == 0){
controller.finishCallback();
try{
controller.worker.close();
for(var iW=0;iW < controller.workers.length; iW++){
controller.workers[iW].close();
}
}
catch(e){
}
}
};
};
this.startSerial = function(){
this.startParallel(1);
};
this.startParallel = function(max){
if(!max){
max = 4;
}
this.finishCounter = this.workerData.length;
this.worker = new Worker(this.workerScript);
for(var iW=0; iW<max; iW++){
var worker = new Worker(this.workerScript);
worker.addEventListener('message', this.callbackWrapper(this), false);
this.workers.push(worker);
}
var factor = this.workerData.length / max;
for(var i=0;i<this.workerData.length;i++){
var workerIndex = Math.floor(i/factor);
this.workers[workerIndex].postMessage(
{
workload: this.workerData.msg,
index:i
});
}
};
}
man kann theoretisch jedem Workload eine eigene Callback-Function mit geben. Wobei eine allgemeine für alle wohl meistens reichen wird.
Wenn alle Workloads abgearbeitet sind wird die finale Callback-Function aufgerufen, um die Ergebnisse weiter zu verarbeiten.
Gerade beim Laden großer Datenmengen wie Lagerstrukturen oder bei Exporten ganzer Datenbestände, kommt man schnell in Bereiche, wo das Laden und Aufbereiten mehr als ein paar Minuten dauern kann. Bei normalen Desktop-Programmen und Skripten ist das nicht ganz so relevant. Wenn man aber im Web-Bereich arbeitet, kann es schnell zu Problemen mit Timeouts kommen. 3 Minuten können hier schon ein extremes Problem sein. Asynchrone Client helfen hier, aber keiner Wartet gerne und Zeit ist Geld.
Es gibt dann einen Punkt, wo die Zeit pro zu ladenen Element sich nicht mehr weiter verringern lässt. Hier hilft dann nur noch die Verarbeitung der einzelnen Elemente zu parallelisieren. Bei Java gibt es den ExecutorService, in JavaScript die WebWorker und in PHP gibt es pthreads. Es ist nichts für Leute mit einem Shared-Hosted Webspace, weil man eine Extension nach installieren muss.
Installation:
http://php.net/manual/de/pthreads.installation.php
(es sollte die erste DLL reichen)
Hier ist ein kleines Beispiel wo eine Liste von MD5-Hashes erzeugt werden soll. Die Threaded-Variante lief bei mir meistens fast doppelt so schnell
wie die einfache Variante mit der Schleife.
<?php
$files=scandir("c:/xampp/htdocs/files");
$start=microtime();
$cnt=count($files);
$result=array();
for($i=0;$i<$cnt;$i++){
$file=$files[$i];
if($file!="." && $file!=".."){
$result[count($result)]=md5(file_get_contents("c:/xampp/htdocs/files/".$file));
}
}
echo "bench (loop): ".(microtime()-$start)."<br>";
class PWorker extends Collectable{
public $workload;
public function __construct($work){
$this->workload=$work;
}
public function run(){
$this->workload=md5(file_get_contents("c:/xampp/htdocs/files/".$this->workload));
$this->setGarbage();
}
}
$result=array();
$start=microtime();
$p = new Pool(4);
for($i=0;$i<$cnt;$i++){
$file=$files[$i];
if($file!="." && $file!=".."){
$p->submit(new PWorker($file));
}
}
$p->shutdown();
$p->collect(function($checkingTask){
global $result;
$result[count($result)]=$checkingTask->workload;
return $checkingTask->isGarbage();
});
echo "bench (4 threads): ".(microtime()-$start)."<br>";
?>
Man kann also auch in PHP moderne Anwendungen schrieben die Multi-Core CPUs auch wirklich ausnutzen können und muss sich hinter Java in den meisten Bereichen nicht mehr verstecken.