IOException.de

Icon

Ausgewählter Nerdkram von Informatikstudenten der Uni Ulm

Barrier points in Node.js

Node.js ist ein serverseitiges, hochskalierbares Framework für die Entwicklung von asynchronen Netzwerkanwendungen in JavaScript. Aufgrund der Asynchronität ist das Framework in der Lage, tausende Verbindung gleichzeitig offen zu halten und zu verarbeiten. Ein wesentliches Merkmal hier von ist, dass ausschließlich mit Callbacks gearbeitet wird, um auf Beendigungen von Operationen zu reagieren.

Im Normalfall führt das zu einem Chaining von Callback. Im folgenden Beispiel sollen zwei Dateien geöffnet werden und ihr kompletter Inhalt zurückgegeben werden:

var size = 0;

fs.readFile('/home/benjamin/file1', function(err, data)
{
	if (!err)
	{
		// first file read
		size += data.length;
		fs.readFile('/home/benjamin/file2', function(err, data)
		{
			if (!err)
			{
				// second file read
				size += data.length;
				fs.writeFile('/home/benjamin/size', size, function(err)
				{
					if (!err)
					{
							// both file read and content written to file
							sys.log('done');
						}
					});
			}
		});
	}
});

Das Problem hierbei ist, dass zwei Aktionen, die eigentlich parallel ablaufen könnten, nämlich das lesen beider Dateien, nacheinander ablaufen müssen. Für einen parallelen Ablauf ist eine zusätzliche Koordination notwendig, da die Resultate beider Aufrufe in Verbindung stehen. Hierfür habe ich mich an den CyclicBarrier Klassen von Java orientiert und eine einfache Klasse für die Koordination von asynchronen Callbacks geschrieben. Eine Barriere wird erzeugt unter Angabe der Anzahl von teilnehmenden Parties. Desweiteren können optional ein Callback für die Beendigung sowie ein optionaler Callback im Abbruchsfall registriert werden. Anschließend können die einzelnen Aufgaben der Parties angestoßen werden. Im Erfolgsfall müssen diese an der Barriere mit submit() anzeigen, dass sie beendet sind. Durch abort() kann auch ebenfalls ein Abbruch signalisiert werden. Eine Propagation der Ergebnisdaten lässt am besten durch eine externe Variable realisieren, auf den die Funktionen ebenfalls Zugriff haben.

Der Code des Beispielszenarios ändert sich dann wie folgt:

var size = 0;

var b = new Barrier(2, function()
{
	//Success callback
	fs.writeFile('/home/benjamin/size', size, function(err)
	{
		sys.log('done');
	});
}, function()
{
	//Aborted callback
	sys.log("aborted");
});

fs.readFile('/home/benjamin/file1', function(err, data)
{
	if (err)
	{
		b.abort();
	}
	else
	{
		size += data.length;
		b.submit();
	}
});

fs.readFile('/home/benjamin/file2', function(err, data)
{
	if (err)
	{
		b.abort();
	}
	else
	{
		size += data.length;
		b.submit();
	}
});

Wie im Vergleich zu erkennen ist, lässt sich die Ausführung deutlich beschleunigen. Ein paar Rahmenbedingungen gibt es jedoch. Zunächst dürfen die Teilaufgaben keine Abhängigkeiten untereinander besitzen. Dann darf ein Abbruch einer Teilaufgabe keine Auswirkung auf noch laufende Teilaufgaben besitzen, dessen Ergebnis später verworfen wird. Schließlich muss bei der Programmierung darauf geachtet werden, dass in jedem Fall eine Teilaufgabe mit submit() oder abort() terminiert, da ansonsten ein Lock entsteht.

Die Klasse ist relativ einfach:

/**
 * @class
 * 
 * Creates a new barrier for the given amount of parties. 
 * @param parties
 * @param barrierCallback
 * @param abortCallback
 * @return
 */
var Barrier =  function(parties, barrierCallback, abortCallback)
{
	this.parties = parties;
	this.barrierCallback = barrierCallback;
	this.abortCallback = abortCallback;

	this.running = true;
	this.count = 0;
};

/**
 * Signals a completion of one of the parties.
 * @return
 */
Barrier.prototype.submit = function()
{
	if (++this.count === this.parties && this.running)
	{
		this.barrierCallback();
	}
};

/**
 * Signals an abort by one of the parties. If not callback is passed, the default abort callback will be executed.
 * @param customAbortCallback Optional callback that should be executed due to the abort.
 * @return
 */
Barrier.prototype.abort = function(customAbortCallback)
{
	if (this.running && customAbortCallback)
	{
		customAbortCallback();
	}
	else if (this.running && this.abortCallback)
	{
		this.abortCallback();
	}
	this.running = false;
};

Klasse auf github: http://gist.github.com/464179

Kategorie: javascript

Tags: , , ,

Diese Icons verlinken auf Bookmark Dienste bei denen Nutzer neue Inhalte finden und mit anderen teilen können.
  • MisterWong
  • Y!GG
  • Webnews
  • Digg
  • del.icio.us
  • StumbleUpon
  • Reddit
  • Facebook

3 Kommentare

  1. Andi Kalsch sagt:

    Nach kurzem Überfliegen erinnert mich das an flow.js und step.js. Ich nutze Step: http://github.com/creationix/step

  2. Benjamin Erb sagt:

    Ich habe mir creationix Library angeschaut und sie hat in der Tat Ähnlichkeiten, wenngleich sie noch viel mehr kann. Allerdings benötige ich für meine Anwendungszwecke, der parallelen Ausführung vieler nicht voneinander abhängiger Aufgaben, detaillierte Fehlerbehandlungen, die ich notfalls auch beim abbrechen selbst noch bestimmen kann. Insofern unterscheiden sich die Lösungen hier doch etwas.

  3. Daniel Lo sagt:

    Grüße, ich schätze Ihre Computer-Programmierung-Code. Könnten Sie bitte fügen Sie die MIT-Lizenz, um Ihren Code?

    Regards,

    Daniel Lo

Kommentar