Multithreading-Eigenschaften von LabVIEW-Funktionen und -Treibern

Überblick

Dieses Dokument ist Teil der Serie zu den „Grundlagen der Multicore-Programmierung

Grundlagen der Multicore Programmierung
Serie zu den „Grundlagen der Multicore-Programmierung“


Die Programmierung Multithreading-fähiger Anwendungen ist eine Schlüsselmethode für die Ausnutzung von Multicore-Prozessoren. Wird eine Anwendung in mehrere Threads unterteilt, kann das Betriebssystem diese über mehrere auf einem PC verfügbare Prozessorkerne (Cores) hinweg aufteilen. Dieses Dokument befasst sich mit den im Zusammenhang mit Multicore-Prozessoren nutzbaren Vorteilen Multithreading-fähiger und ablaufinvarianter Funktionen und Treibern in NI LabVIEW.

Inhalt

Paralleles oder Multithreading-fähiges Verhalten

In traditionellen Sprachen muss ein Programm in verschiedene Thread geteilt werden, um es parallel auszuführen. Zwar kann jeder Thread zur selben Zeit ablaufen, jedoch besteht ein Unterschied zwischen dem Schreiben von Code, der in einer Multithreading-fähigen Anwendung ablaufen kann, und Code, der parallel ausgeführt wird, denn nur der parallele Code nutzt die Leistungsfähigkeit von Multicore-Prozessoren optimal aus. Dieser Unterschied kommt oft in den Treibern oder Funktionen zum Ausdruck, die der Anwender eventuell für das Schreiben von Programmen verwendet. Multithreading-fähige Funktionen können aus mehreren Threads aufgerufen werden und überschreiben ihre Daten nicht, was Konflikte vermeidet, die durch das Blockieren der Ausführung hervorgerufen werden. Ruft ein Thread die Funktion auf, muss ein anderer Thread, der dasselbe versucht, solange warten, bis der erste Thread abgearbeitet ist. Ablaufinvariante Funktionen gehen noch einen Schritt weiter und machen es möglich, dass mehrere Threads dieselbe Funktion zur gleichen Zeit parallel aufrufen und ausführen. Beide Beispiele werden in einem Multithreading-fähigen Programm korrekt ausgeführt, doch geht das mit ablaufinvarianten Funktionen schneller, da diese gleichzeitig ablaufen.

Ablaufinvariante Funktionen

Es gibt Fälle und Umgebungen, in denen unter Umständen eine nicht ablaufinvariante Funktion oder ein solches Programm notwendig sind, um Probleme mit dem Zugriff auf Funktionen zu vermeiden. Viele Multithreading-fähige Bibliotheken sorgen für ihre eigene „Sicherheit“, indem sie Ressourcen sperren. Das bedeutet, wenn ein Thread eine Funktion aufruft, dann wird diese Funktion oder sogar die gesamte Bibliothek gesperrt, so dass sie von keinem anderen Thread genutzt werden kann. In einer parallelen Ausführungssituation, wenn zwei verschiedene Codepfade bzw. Threads versuchen, ein und dieselbe Bibliothek bzw. Funktion zur gleichen Zeit aufzurufen, bedeutet dies, dass die Sperrung einen der Threads zum Stillstand bringt, bis der andere Thread vollständig ausgeführt wurde. Darüber hinaus wird Speicherplatz gespart, wenn nur ein Thread auf einmal auf eine Funktion zugreifen kann, da keine zusätzlichen Instanzen notwendig sind.

Jedoch kann, wie oben erwähnt, die Kombination paralleler Techniken mit Ablaufinvarianz in Funktionen die Leistung des Codes steigern.

Da Gerätetreiber mit LabVIEW (wie etwa NI-DAQmx) sowohl Multithreading-fähig als auch ablaufinvariant sind, kann eine Funktion von mehreren Threads gleichzeitig aufgerufen und immer noch korrekt ausgeführt werden, ohne zu blockieren. Dies ist eine wichtige Funktion zum Schreiben parallelen Codes und zum Optimieren der Leistung auf Multicore-Systemen. Falls Code ohne ablaufinvariante Ausführung benutzt wird, könnte das ein Grund dafür sein, dass die Leistung nicht besser geworden ist: Der Code muss warten bis die anderen Threads mit einer Funktion fertig sind, um sie aufrufen zu können. Dieser Punkt wird durch die LabVIEW-Funktion VI-Hierarchie (Show VI Hierarchy) verdeutlicht. Um die Hierarchie eines einzelnen VIs zu sehen, muss Ansicht >> VI-Hierarchie (View >> VI Hierarchy) gewählt werden. In der in Abbildung 1 dargestellten VI-Hierarchie hängen F1 und F2 vom selben VI ab (in diesem Fall ein sehr rechenintensiver Algorithmus für die Fast-Fourier-Transformation). Wenn F1 und F2 parallel ausgeführt werden sollen, ist es wichtig, dass dieses VI ablaufinvariant ist.

Abbildung 1: Ansicht der VI-Hierarchie in LabVIEW

Die Ablaufinvarianz ist ein wichtiger Faktor für die Eliminierung unnötiger Abhängigkeiten im Programmcode. Manche Analyse-VIs in LabVIEW sind standardmäßig ablaufinvariant, andere nicht. Deshalb müssen die Eigenschaften dieser VIs betrachtet werden, um sicherzugehen, dass sie parallel ausgeführt werden.

Konfiguration von LabVIEW

Um ein LabVIEW-VI ablaufinvariant zu machen, ist aus dem Dropdown-Menü Datei >> VI-Einstellungen (File >> VI Properties) und dann Ausführung (Execution) zu wählen. Jetzt kann man das Kästchen neben Ablaufinvariante Ausführung (Reentrant Execution) anklicken und eine Kopie-Option wählen.

 Abbildung 2: Option für die ablaufinvariante Ausführung im Dialogfeld VI-Einstellungen

LabVIEW unterstützt zwei Arten von ablaufinvarianten VIs. Mit der Option Kopie für jede Instanz vorbelegen (Preallocate clone for each instance) wird ein Klon-VI für jeden Aufruf des ablaufinvarianten VIs erstellt, bevor LabVIEW dieses VI aufruft, oder wenn ein Klon-VI die Zustandsinformation über mehrere Aufrufe hinweg aufrechterhalten soll. Enthält zum Beispiel ein ablaufinvariantes VI ein nicht initialisiertes Schieberegister oder eine lokale Variable, Einstellung oder Methode, die Werte enthält, die für künftige Aufrufe des Klon-VIs benötig werden, kann man die Option Kopie für jede Instanz vorbelegen wählen. Diese Option wird auch gewählt, wenn das ablaufinvariante VI die Funktion Erster Aufruf (First Call) enthält. Auch für VIs auf Systemen mit LabVIEW Real-Time wird diese Einstellung empfohlen, um Jitter minimal zu halten.

Mit der Option Kopien zwischen Instanzen austauschen (Share clones between instances) lässt sich die Speicherauslastung im Zusammenhang mit der Vorbelegung zahlreicher Klon-VIs reduzieren. Bei Auswahl dieser Option erstellt LabVIEW erst dann ein Klon-VI, wenn ein VI das ablaufinvariante VI aufruft. LabVIEW erstellt Klon-VIs also erst auf Nachfrage, was bei der Ausführung des VIs zu Jitter führen kann. LabVIEW erhält Statusinformationen nicht über alle Aufrufe des ablaufinvarianten VIs hinweg.

Fähigkeiten von LabVIEW-Treibern

Bei der Anbindung an Hardware ist es wichtig, Thread-sichere und ablaufinvariante Treiber zu benutzen. Damit ist es möglich, in Form von Leistungssteigerungen von der Multicore-Technologie zu profitieren.

Bei früheren LabVIEW-Versionen sind Gerätetreiber nicht in allen Fällen ablaufinvariant. Der traditionelle Treiber NI-DAQ z. B. ist insoweit Multithreading-fähig, dass keine Fehlermeldung verursacht wird, wenn zwei verschiedene Threads gleichzeitig auf ihn zugreifen. Der Treiber reagiert auf diese Anfrage mit einer Sperrung, d. h. sobald ein Thread eine NI-DAQ-Funktion aufruft, werden alle anderen Threads in eine Warteposition gesetzt, bis die Funktion komplett ausgeführt wurde.

NI-DAQmx lässt sich hingegen wesentlich eleganter in einer parallelen Multithreading-fähigen Umgebung einsetzen. NI-DAQmx ist ablaufinvariant und erlaubt das gleichzeitige Aufrufen durch mehrere Threads. So können zwei verschiedene Analogeingänge von zwei verschiedenen Karten in zwei verschiedenen Threads im gleichen Programm ausgeführt werden. Beide laufen simultan ab, ohne dass es zu einer Blockierung kommt. Darüber hinaus können ein Analog- und ein Digitaleingang von zwei ganz verschiedenen Threads auf derselben Karte zur gleichen Zeit ausgeführt werden. Dabei wird aufgrund der Fähigkeiten des Treibers NI-DAQmx eine einzige Hardwarequelle eigentlich als zwei verschiedene Quellen behandelt.

Treiber für modulare Messgeräte von NI funktionieren wie NI-DAQmx. Alle in Tabelle 1 aufgeführten Treiber sind sowohl Thread-sicher als auch ablaufinvariant. Sie ermöglichen alle, dieselbe Funktion zweimal auf zwei verschiedenen Geräten zur selben Zeit aufzurufen. Besonders für große Systeme mit Programmcode, der auf mehrere Messgeräte zugreift, ist das von Vorteil.

Tabelle 1: Thread-sichere und ablaufinvariante Treiber für modulare Messgeräte