Multithread-Programmierung ist ein wichtiger Ansatz zur Nutzung von Multicore-Prozessoren. Durch die Unterteilung Ihrer Anwendung in mehrere Threads kann das Betriebssystem diese Threads über mehrere auf dem PC verfügbare Prozessorkerne hinweg ausgleichen bzw. planen. In diesem Dokument werden die Vorteile der Verwendung von multithreadsicheren und ablaufinvarianten Funktionen und Treibern für Multicore-Prozessoren in NI LabVIEW erläutert.
In herkömmlichen Sprachen müssen Sie Ihr Programm für die parallele Ausführung in verschiedene Threads unterteilen. Jeder Thread kann zur selben Zeit ausgeführt werden. Es gibt jedoch einen Unterschied zwischen dem Schreiben von Programmcode, der sicher in einer Multithread-Anwendung ausgeführt werden kann, und dem Schreiben von Programmcode, der parallel ausgeführt wird, um die Leistung eines Multicore-Systems zu maximieren. Dieser Unterschied zeigt sich oft in den Treibern oder Funktionen, die Sie beim Schreiben von Programmen verwenden können. Multithreadsichere Funktionen können von mehreren Threads aus aufgerufen werden und überschreiben ihre Daten nicht, wodurch Konflikte durch Blockieren der Ausführung verhindert werden. Wenn ein Thread die Funktion aufruft, müssen alle anderen Threads, die versuchen, die Funktion aufzurufen, warten, bis der erste Thread beendet ist. Ablaufinvariante Funktionen gehen noch einen Schritt weiter, indem mehrere Threads dieselbe Funktion gleichzeitig (parallel) aufrufen und ausführen können. Beide Beispiele werden in einem Multithread-Programm ordnungsgemäß ausgeführt. Mit ablaufinvarianten Funktionen können Sie jedoch die Ausführung beschleunigen, da diese gleichzeitig ausgeführt werden.
In LabVIEW sind die über Verbindungen übertragenen Daten in der Regel unabhängig von den Funktionen, die mit den Daten arbeiten. Laut Definition ist der Zugriff auf die Daten einer Verbindung für VIs oder Funktionen, die diese Verbindung nicht nutzen, schwierig. Bei Bedarf erstellt LabVIEW beim Trennen einer Verbindung eine Kopie der Daten, so dass es unabhängige Versionen der Daten gibt, mit denen nachfolgende VIs arbeiten können. Darüber hinaus sind die meisten LabVIEW-VIs und -Treiber sowohl multithreadsicher als auch ablaufinvariant. Allerdings können Sie einige der integrierten Bibliotheks-VIs in LabVIEW als ablaufvariant konfigurieren. Da ablaufinvariante VIs mehr Speicher benötigen, müssen Sie möglicherweise einen Kompromiss zwischen Speichernutzung und Parallelität eingehen. In vielen Fällen entscheidet sich LabVIEW standardmäßig für Speichereinsparungen oder die ablaufvariante Konfiguration und lässt Sie entscheiden, ob maximale Parallelität das Ziel sein soll. Falls ja, können die Bibliotheks-VIs leicht zu einer ablaufinvarianten Einstellung wechseln.
Es gibt Fälle und Umgebungen, in denen Sie möglicherweise eine ablaufvariante Funktion oder ein entsprechendes Programm verwenden müssen, um Probleme beim Zugriff auf Funktionen zu vermeiden. Viele multithreadsichere Bibliotheken garantieren ihre „Sicherheit“, indem sie Ressourcen sperren. Das heißt, wenn ein Thread die Funktion aufruft, ist die Funktion oder sogar die gesamte Bibliothek gesperrt, so dass sie von keinem anderen Thread aufgerufen werden kann. Wenn in einer parallelen Situation zwei verschiedene Pfade Ihres Programmcodes (oder Threads) versuchen, dieselbe Bibliothek oder Funktion aufzurufen, ist aufgrund der Blockierung einer der Threads gezwungen, zu warten, oder der Thread wird blockiert, bis der andere abgeschlossen ist. Wenn nur jeweils ein Thread auf eine Funktion zugreifen kann, wird ebenfalls Speicherplatz eingespart, da keine zusätzlichen Instanzen benötigt werden.
Wie bereits erwähnt, kann die Kombination paralleler Programmiertechniken mit Ablaufinvarianz in Ihren Funktionen jedoch dazu beitragen, die Ausführungsgeschwindigkeit Ihres Programmcodes zu erhöhen.
Da Gerätetreiber mit LabVIEW (wie z. B. NI-DAQmx) sowohl multithreadsicher als auch ablaufinvariant sind, kann Ihre Funktion von mehreren Threads gleichzeitig aufgerufen werden und weiterhin ohne Blockierung ordnungsgemäß funktionieren. Dies ist ein wichtiges Feature für das Schreiben von parallelem Programmcode und zur Optimierung der Leistung mit Multicore-Systemen. Wenn Sie Programmcode ohne ablaufinvariante Ausführung verwenden, hat sich Ihre Leistung möglicherweise nicht verbessert: Ihr Programmcode muss warten, bis die anderen Threads mit den einzelnen Funktionen fertig sind. Erst dann kann er auf sie zugreifen. Um diesen Aspekt weiter zu verdeutlichen, sollten Sie sich das Feature VI-Hierarchie in LabVIEW ansehen. Zur Anzeige der Hierarchie eines individuellen VIs wählen Sie Ansicht >> VI-Hierarchie aus. In der in Abbildung 1 dargestellten VI-Hierarchie sind sowohl F1 als auch F2 vom selben VI abhängig (in diesem Fall ein sehr rechenintensiver Algorithmus zur schnellen Fourier-Transformation). Es ist wichtig, dass dieses VI ablaufinvariant ist, wenn F1 und F2 parallel ausgeführt werden sollen.
Abbildung 1 – VI-Hierarchieansicht in LabVIEW
Ablaufinvarianz ist eine wichtige Überlegung, um unnötige Abhängigkeiten in Ihrem Programmcode zu beseitigen. Einige Analyse-VIs in LabVIEW sind standardmäßig ablaufvariant oder ablaufinvariant. Daher ist es wichtig, die Eigenschaften dieser VIs anzuzeigen, um sicherzustellen, dass sie parallel ausgeführt werden.
Um Ihr LabVIEW-VI ablaufinvariant zu konfigurieren, wählen Sie Datei >> VI-Eigenschaften und anschließend Ausführung aus dem Dropdown-Menü aus. Sie können nun das Kästchen neben Ablaufinvariante Ausführung aktivieren und entscheiden, welche Kopieroption Sie auswählen möchten.
Abbildung 2 – Option „Ablaufinvariante Ausführung“ im Dialogfeld „VI-Eigenschaften“
LabVIEW unterstützt zwei Arten von ablaufinvarianten VIs. Wählen Sie die Option Kopie für jede Instanz vorbelegen aus, wenn Sie für jeden Aufruf des ablaufinvarianten VIs eine VI-Kopie erstellen möchten, bevor LabVIEW das ablaufinvariante VI aufruft, oder wenn die VI-Kopie Zustandsangaben über Aufrufe hinweg beibehalten muss. Wenn ein ablaufinvariantes VI beispielsweise ein nicht initialisiertes Schieberegister oder eine lokale Variable, Eigenschaft oder Methode enthält, deren Werte für zukünftige Aufrufe der VI-Kopie beibehalten werden müssen, wählen Sie die Option Kopie für jede Instanz vorbelegen aus. Wählen Sie diese Option auch aus, wenn das ablaufinvariante VI die Funktion Erster Aufruf? enthält. Diese Einstellung wird auch für VIs empfohlen, die auf LabVIEW Real-Time-Systemen ausgeführt werden, um minimalen Jitter zu gewährleisten.
Wählen Sie die Option Kopien mit anderen Instanzen austauschen aus, um den Speicherbedarf bei der Vorbelegung zahlreicher VI-Kopien zu reduzieren. Wenn Sie die Option Kopien mit anderen Instanzen austauschen auswählen, erstellt LabVIEW die VI-Kopie erst, wenn ein VI das ablaufinvariante VI aufruft. Mit dieser Option erstellt LabVIEW die VI-Kopien auf Anforderung, was möglicherweise zu Jitter in der Ausführung des VIs führt. LabVIEW behält Zustandsangaben nicht über Aufrufe des ablaufinvarianten VIs hinweg bei.
Threadsichere und ablaufinvariante Treiber sind wichtig, wenn Sie mit Hardware arbeiten. Mit diesen Attributen können Sie die Vorteile der Multicore-Technologie zur Leistungsverbesserung nutzen.
Bei vorherigen Versionen von LabVIEW war Ablaufinvarianz bei Gerätetreibern nicht immer selbstverständlich. Traditional NI-DAQ beispielsweise war multithreadsicher, da dieser Treiber nicht versagt hat, wenn er von zwei verschiedenen Threads gleichzeitig aufgerufen wurde. Dazu wurde eine globale Sperre eingesetzt, d. h., sobald ein Thread eine NI-DAQ-Funktion aufgerufen hatte, mussten alle anderen Threads warten, bis diese Funktion abgeschlossen war, bevor sie NI-DAQ-Funktionen ausführen konnten.
Alternativ dazu kann NI-DAQmx wesentlich eleganter in einer parallelen Multithread-Umgebung eingesetzt werden. NI-DAQmx ist ablaufinvariant, da mehrere Threads den Treiber gleichzeitig aufrufen können. Sie können zwei verschiedene analoge Erfassungen von zwei verschiedenen Platinen von verschiedenen Threads im selben Programm ausführen – beide gleichzeitig, ohne zu blockieren. Darüber hinaus können Sie eine analoge und eine digitale Erfassung von zwei komplett unterschiedlichen Threads gleichzeitig auf derselben Platine ausführen. Dadurch wird eine einzelne Hardware-Ressource aufgrund der Komplexität des NI-DAQmx-Treibers effektiv wie zwei separate Ressourcen behandelt.
Modulare Gerätetreiber von NI funktionieren ebenfalls wie NI-DAQmx. Alle in Tabelle 1 aufgeführten Treiber sind threadsicher und ablaufinvariant. Sie alle bieten die Möglichkeit, zwei der gleichen Funktionen auf zwei verschiedenen Geräten gleichzeitig aufzurufen. Dies ist insbesondere für große Systeme mit Programmcode und mehreren verwendeten Geräten von Vorteil.
Tabelle 1 – Threadsichere und ablaufinvariante modulare Gerätetreiber
Um bei Verwendung der parallelen Programmierung die Vorteile von Multicore-Architekturen zu nutzen, ist es wichtig, nicht nur die Programmiersprache und alle Aspekte hinsichtlich des Schreibens von parallelem Programmcode zu berücksichtigen, sondern auch sicherzustellen, dass Ihre Treiber und Funktionen für eine parallele Umgebung geeignet sind.