Parallel Computing (Teil 2)
Kommentare

Eingebauter Singleton
Die Task Parallel Library beinhaltet im Bereich der Datenstrukturen für die parallele Programmierung so genannte verzögerte Initialisierungsklassen. Dabei handelt es sich um die

Eingebauter Singleton

Die Task Parallel Library beinhaltet im Bereich der Datenstrukturen für die parallele Programmierung so genannte verzögerte Initialisierungsklassen. Dabei handelt es sich um die folgenden drei zum Teil generischen Klassen:

  • System.Lazy
  • System.Threading.ThreadLocal
  • System.Threading.LazyInitializer

Mithilfe der generischen Klasse Lazykann auf sehr einfacher Art und Weise ein Singleton realisiert werden. Die Klasse Lazy ermöglicht die verzögerte, einmalige und Thread-sichere Initialisierung einer Variablen. Die Bereitstellung des gewünschten Werts – wobei es sich hier um einen Wert- sowie Referenztypen handeln kann – erfolgt über eine Funktion. Diese Initialisierungsfunktion muss bei der Anlage der Lazy -Instanz übergeben werden. Dieser Mechanismus ermöglicht die einfache Umsetzung einer Thread-sicheren Singleton-Klasse. Wie in Listing 4 zu erkennen ist, hat sich der notwendige Implementierungsaufwand erheblich reduziert. Positiv ist auch, dass keine Sperren mehr selbst verwaltet werden müssen. Die einmalige Initialisierung des gewünschten Objekts erfolgt automatisch, sobald auf die Eigenschaft Value der Lazy Instanz self zugegriffen wird. Dies geschieht in der statischen Eigenschaft Create, die für die Rückgabe einer gültigen Instanz zuständig ist. Da die Eigenschaft Value selbst dafür sorgt, dass nur eine Instanz erstellt wird, sind keine weiteren Abfragen notwendig. Weitere Informationen zu der Klasse Lazy sind unter [2] erhältlich.

public class SingletonClass
{
  private static readonly Lazy self = 
    new Lazy(() => new SingletonClass());
  private SingletonClass()
  { }
  public static SingletonClass Instance
  {
    get
    {
      return self.Value;
    }
  }
}

  

LazyInitializer

Das Singleton-Entwurfsmuster kann sehr gut in eigenen Klassen verwendet werden, wenn der Quellcode verfügbar und änderbar ist. Verwendet man allerdings Klassen aus einer bereits kompilierten Bibliothek, auf die kein Zugriff besteht, gestaltet sich die Umsetzung eines Singleton etwas schwieriger. Um eine bereits implementierte Klasse aus einem Third Party API im Nachhinein als Singleton bereitzustellen, existieren generell zwei Möglichkeiten. Eine Möglichkeit besteht darin, eine zusätzliche Klasse umzusetzen, die als Wrapper eingesetzt wird. Diese Wrapper-Klasse ist selbst ein Singleton und gibt lediglich eine Instanz der gewünschten Klasse zurück. Um auf die Implementierung einer zusätzlichen Klasse zu verzichten, besteht aber auch die Möglichkeit, die Klasse LazyInitializer einzusetzen. Diese Klasse ermöglicht die einmalige und verzögerte Initialisierung einer Klasse (Listing 5).

Listing 5
public class UsingLazyInit
{
    private static DataBase db;
    public static void LazyInit()
    {
      LazyInitializer.EnsureInitialized(ref db, 
      () => { return new DataBase (); });
    }
}
  

Die Variable vom Typ Database gilt es nur einmalig zu initialisieren. Dazu kommt nun in der Methode LazyInit die statische Methode EnsureInitialized zum Einsatz. Dieser generischen Methode werden der gewünschte Datentyp sowie die Referenz auf das zu initialisierende Objekt übergeben. Wie im Beispiel zu sehen ist, kann auch optional eine Initialisierungsfunktion spezifiziert werden. Diese wird beim einmaligen Anlegen des Objekts aufgerufen. Somit können hier zusätzliche Aktionen vor der Initialisierung durchgeführt werden. Die Angabe einer Initialisierungsfunktion ist optional, wird sie ausgelassen, so wird lediglich die angeforderte Instanz über den Standardkonstruktor erstellt. Weitere Informationen zu der Klasse LazyInitializer sind unter [3] erhältlich.

Einmalig pro Thread

Die bisherigen Implementierungen haben die Einmaligkeit einer Objektinstanz für die gesamte Anwendung sichergestellt. Teilweise ist es aber auch notwendig, eine eindeutige Objektinstanz pro Thread erstellen zu können. Hierfür eignet sich in der Regel der lokale Speicher des Threads (Thread local storage) sehr gut. Genau für diesen Anwendungsfall kann die generische Klasse ThreadLocal verwendet werden. Sie speichert eine Objektinstanz in Abhängigkeit von einem Thread. Die Verwendung der Klasse ist vergleichbar mit der Klasse LazyInitializer. Listing 6 zeigt, wie die Klasse eingesetzt wird.

Listing 6
private static ThreadLocal ThreadLocal = new ThreadLocal(() =>
  {
    return new Help();
  });
Task.Factory.StartNew(() =>
{                
  for (int i = 0; i < 10; i++)
  {
    ThreadLocal.Value.ThreadName = Thread.CurrentThread.
    ManagedThreadId.ToString();
    ...
  }
});
  

Zu Beginn wird eine Instanz der Klasse ThreadLocal angelegt und der gewünschte Typ - in diesem Fall die Klasse Help - wird übergeben. Danach wird eine (optionale) Initialisierungsfunktion überreicht. Sobald nun auf die Eigenschaft Value der Instanz ThreadLocal zugegriffen wird, wird eine Instanz der Klasse Help erzeugt. Über die Value -Eigenschaft besteht danach sofort Zugriff auf die neue Instanz. Dieser Vorgang ist für alle Threads gültig, sodass jeder Thread eine eigene Instanz vom Typ Help hat. Weitere Informationen zu der Klasse ThreadLocal sind unter [4] erhältlich.

Ausblick

Der nächste Teil dieser Parallel-Computing-Serie beschreibt die Funktionsweise und Umsetzung des Producer/Consumer-Entwurfsmusters.

Marc André Zhou arbeitet als Senior Consultant bei der Logica Deutschland GmbH & Co. KG. Seine Schwerpunkte liegen in den Bereichen SharePoint, Softwarearchitekturen und Frameworks, hier hauptsächlich im .NET-Umfeld. Sie erreichen ihn unter marc.andre.zhou@logica.com.
Serienüberblick "Parallel Computing"
  • Teil 1: Singleton und InitOnce
  • Teil 2: Producer/Consumer-Entwurfsmuster
  • Teil 3: Futures
  • Teil 4: Parallel Loops
  • Teil 5: Parallel Aggregation
  • Teil 6: Value Object
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -