Semaphoren und Signale sind Tools in der 4D Programmiersprache, um Interaktionen zu verwalten und bei einer Anwendung mit mehreren Prozessen Konflikte zwischen den Prozessen zu vermeiden. Sie unterscheiden sich wie folgt:
Mit einer Semaphore können Sie sicherstellen, dass zwei oder mehrere Prozesse nicht gleichzeitig dieselbe Ressource ändern. Nur der Prozess, der die Semaphore setzt, kann sie auch wieder entfernen.
Mit einem Signal können Sie sicherstellen, dass ein oder mehrere Prozesse abwarten, bis eine spezifische Aufgabe abgeschlossen ist, bevor die Ausführung weitergeht. Alle Prozesse können warten und/oder ein Signal auslösen.
In einem Computerprogramm ist eine Semaphore ein Tool zum Schutz von Aktionen, die nur von einem Prozess oder einem Benutzer zur selben Zeit ausgeführt werden dürfen.
In 4D sind Semaphoren beispielsweise beim Ändern eines Interprozess Arrays notwendig: Ändert ein Prozess die Werte des Array, darf kein anderer Prozess die Möglichkeit haben, zum gleichen Zeitpunkt dasselbe zu tun. Der Entwickler verwendet eine Semaphore, um einem Prozess anzuzeigen, dass er die Operationen nur ausführen kann, wenn kein anderer Prozess gerade dieselben Aktionen ausführt. Stößt ein Prozess auf eine Semaphore, gibt es drei Möglichkeiten:
Er erhält sofort das Recht zur Ausführung
Er wartet, bis er an der Reihe ist und das Recht zur Ausführung erhält
Er setzt seinen Weg fort und verwirft die Idee, diese Aufgaben auszuführen
Die Semaphore schützt also Teile des Code. Sie erlaubt nur einen Prozess zur selben Zeit und blockiert den Zugriff, bis der aktuell berechtigte Prozess seinen Vorrang durch Freigabe der Semaphore beendet.
In 4D setzen Sie eine Semaphore durch Aufrufen der Funktion Semaphore. Um sie wieder freizugeben, rufen Sie den Befehl CLEAR SEMAPHORE auf.
Die Funktion Semaphore hat ein sehr spezielles Verhalten, da sie zwei Aktionen gleichzeitig durchführt:
Ist die Semaphore bereits zugewiesen, gibt die Funktion Wahr zurück
Ist die Semaphore nicht zugewiesen, weist die Funktion sie dem Prozess zu und gibt zur gleichen Zeit Falsch zurück.
Diese Doppelaktion derselben Funktion stellt sicher, dass zwischen Testen und Zuweisen der Semaphore keine externe Operation eingefügt werden kann.
Über die Funktion Test semaphore können Sie herausfinden, ob eine Semaphore bereits zugewiesen ist oder nicht. Sie wird hauptsächlich als Teil von langen Operationen verwendet, wie z.B. beim Jahresabschluss in der Buchhaltung. Hier steuert Test semaphore die Oberfläche und verhindert den Zugriff zu bestimmten Operationen, wie z.B. Rechnungsdaten hinzufügen.
Eine Semaphore soll in der gleichen Methode gesetzt und freigegeben werden
Die Ausführung von Code, der durch eine Semaphore geschützt ist, soll so kurz wie möglich sein
Der Code soll über den Parameter tickCount der Funktion Semaphore getaktet sein, um die Auflösung der Semaphore abzuwarten.
Hier ein typischer Code für eine Semaphore:
While(Semaphore("MySemaphore";300)) IDLE End while // hier durch Semaphore geschützten Code setzen CLEAR SEMAPHORE("MySemaphore")
Eine nicht aufgelöste Semaphore kann Teile der Anwendung blockieren. Dieses Risiko lässt sich ausschließen, wenn Sie Semaphoren in derselben Methode setzen und auflösen.
Minimieren von Code, der durch eine Semaphore geschützt ist, erhöht die Durchlässigkeit der Anwendung und verhindert, dass die Semaphore wie ein Flaschenhals wirkt.
Zu guter Letzt optimiert der optionale Parameter tickCount der Funktion Semaphore das Abwarten bis zur Freigabe der Semaphore. Mit diesem Parameter funktioniert der Befehl wie folgt:
Der Prozess wartet das Maximum der angegebenen Zahl Ticks (300 im Beispiel) für die gesetzte Semaphore ab, bis die Code Ausführung zur nächsten Zeile übergeht.
Wird die Semaphore vor Ende dieses Limits aufgelöst, wird das sofort dem Prozess zugewiesen (Semaphore gibt Falsch zurück) und die Code Ausführung wird fortgesetzt.
Wird die Semaphore erst mit Ende dieses Limits aufgelöst, wird die Code Ausführung dann fortgesetzt.
Die Funktion zieht auch Anfragen durch Einrichten einer Schleife vor. Auf diese Weise erhält der erste Prozess, der eine Semaphore abfragt, auch als erster eine.
Beachten Sie, dass die Wartezeit gemäß den Eigenheiten der Anwendung gesetzt wird.
Lokale Semaphoren werden nur von den Prozessen auf derselben Arbeitsstation und nur von dieser erkannt. Sie werden mit einem vorangestellten Dollarzeichen gekennzeichnet, z.B. $MeineSemaphore. Mit lokalen Semaphoren steuern Sie Operationen zwischen Prozessen, die auf einer Arbeitsstation ausgeführt werden. Eine lokale Semaphore kann z.B. den Zugriff auf ein Interprozess-Array steuern, das sich alle Prozesse einer Arbeitsstation oder im 4D Einzelplatzbetrieb teilen.
Globale Semaphoren sind für alle Benutzer und für alle Prozesse zugänglich. Mit globalen Semaphoren verwalten Sie Operationen zwischen Benutzern einer Datenbank im Mehrplatzbetrieb.
Globale und lokale Semaphoren funktionieren nach derselben Logik. Der Unterschied liegt in ihrer Reichweite. Im Client/Server Modus werden globale Semaphoren von allen Prozessen auf allen Clients und Servern gemeinsam genutzt. Eine lokale Semaphore wird nur von Prozessen auf dem Rechner genutzt, wo sie erzeugt wurde. In 4D haben globale und lokale Semaphoren dieselbe Reichweite, da es nur einen Benutzer gibt. Wird die Datenbank jedoch in beiden Setups verwendet, stellen Sie sicher, dass - je nachdem, was ausgeführt werden soll - globale oder lokale Semaphoren verwendet werden.
Hinweis: Wir empfehlen, zum Verwalten lokaler Aspekte für den Client einer Anwendung, wie z.B. die Oberfläche oder ein Array mit Interprozessvariablen, lokale Semaphoren zu verwenden. Verwenden Sie dafür globale Semaphoren, verursacht das nicht nur unnötigen Austausch über das Netzwerk, sondern kann auch andere Client-Rechner beeinträchtigen. Über eine lokale Semaphore können Sie solche unerwünschten Nebenwirkungen vermeiden.
Ein Signal ist ein shared object mit den darin enthaltenen Methoden signal.wait( ) und signal.trigger( ), es muss als Parameter an Befehle übergeben werden, die Worker oder Prozesse aufrufen oder erstellen.
Jeder Worker/Prozess, der die Methode signal.wait( ) des Signals aufruft, wartet mit der Ausführung, bis die Eigenschaft signal.signaledwahr ist. Beim Warten auf ein Signal verwendet der aufrufende Prozess keine CPU, was in Multiprozess-Anwendungen für die Performance sehr interessant ist. Die Eigenschaft signal.signaled wird wahr, wenn ein beliebiger Worker/Prozess die Methode signal.trigger( ) des Signals aufruft.
Beachten Sie, dass die Methode signal.wait( ) zur Vermeidung von blockierenden Situationen auch zurückgeben kann, wenn ein definiertes timeout erreicht ist.
Folgendes Schema zeigt die Verwendung des Objekts signal:
In 4D erstellen Sie ein neues Objekt Signal über den Befehl New signal. Anschließend muss es als Parameter an den Befehl New process oder CALL WORKER übergeben werden, um es zu ändern, wenn die Befehle die Aufgabe, die abgewartet werden soll, beendet haben.
signal.wait( ) muss vom Worker/Prozess aufgerufen werden, der einen anderen Worker/Prozess benötigt, um die Aufgabe zu beenden und fortzufahren
signal.trigger( ) muss vom Worker/Prozess aufgerufen werden, der seine Ausführung beendet, um alle anderen weiter laufen zu lassen
Wurde ein Signal mit einem Aufruf signal.trigger( ) ausgelöst (die Eigenschaft signal.signaled ist wahr), kann es nicht erneut verwendet werden. Wollen Sie ein anderes Signal setzen, müssen Sie die Funktion New signal erneut aufrufen.
Da signal ein shared object ist (siehe Shared Objects und Shared Collections), können Sie es zum Zurückgeben von Ergebnissen von aufgerufenen Workers/Prozessen verwenden, wenn Sie Werte innerhalb einer Struktur Use...End use schreiben (siehe Beispiel).
Ein preemptiver Prozess muss einen Benutzerwert erhalten, z.B. von einem Dialog. Da sich ein Dialog nicht aus einem preemptive Prozess anzeigen lässt, ruft er einen kooperativen Prozess auf und wartet auf den zurückgegebenen Wert:
// Hauptprozess aufrufen und Methode OpenForm ausführen CALL WORKER(1;"OpenForm";$signal) // do another calculation
... // Auf Ende des Prozesses warten $signaled:=$signal.wait()
// Ihrem <em>shared object</em> $signal ein neues Attribut hinzufügen, um Ihre Ergebnisse dem anderen Prozess zu übergeben: Use($signal) $signal.result:=$form.value End use
// Das Signal zum wartenden Prozess auslösen $signal.trigger()