Threading-Modelle

Als oneway markierte Methoden blockieren nicht. Bei Methoden, die nicht als oneway markiert sind, wird der Methodenaufruf eines Clients blockiert, bis der Server die Ausführung abgeschlossen hat oder einen synchronen Rückruf aufruft (je nachdem, was zuerst eintritt). Servermethodenimplementierungen dürfen höchstens einen synchronen Rückruf aufrufen; Zusätzliche Rückrufaufrufe werden verworfen und als Fehler protokolliert. Soll eine Methode per Callback Werte zurückgeben und ruft ihren Callback nicht auf, wird dies als Fehler protokolliert und als Transportfehler an den Client gemeldet.

Threads im Passthrough-Modus

Im Passthrough-Modus sind die meisten Aufrufe synchron. Um jedoch das beabsichtigte Verhalten beizubehalten, dass oneway den Client nicht blockieren, wird für jeden Prozess ein Thread erstellt. Einzelheiten finden Sie in der HIDL-Übersicht .

Fäden in gebundenen HALs

Um eingehende RPC-Aufrufe (einschließlich asynchroner Rückrufe von HALs an HAL-Benutzer) und Todesbenachrichtigungen zu bedienen, ist jedem Prozess, der HIDL verwendet, ein Threadpool zugeordnet. Wenn ein einzelner Prozess mehrere HIDL-Schnittstellen und/oder Todesbenachrichtigungshandler implementiert, wird sein Threadpool von allen gemeinsam genutzt. Wenn ein Prozess einen eingehenden Methodenaufruf von einem Client empfängt, wählt er einen freien Thread aus dem Threadpool aus und führt den Aufruf auf diesem Thread aus. Wenn kein freier Thread verfügbar ist, wird blockiert, bis einer verfügbar ist.

Wenn der Server nur einen Thread hat, werden Aufrufe an den Server der Reihe nach ausgeführt. Ein Server mit mehr als einem Thread führt Aufrufe möglicherweise in einer falschen Reihenfolge aus, selbst wenn der Client nur über einen Thread verfügt. Für ein bestimmtes Schnittstellenobjekt ist jedoch garantiert, dass oneway geordnet sind (siehe Server-Threading-Modell ). Bei einem Multithread-Server, der mehrere Schnittstellen hostet, können oneway an verschiedene Schnittstellen gleichzeitig miteinander oder mit anderen blockierenden Aufrufen verarbeitet werden.

Mehrere verschachtelte Aufrufe werden an denselben Hwbinder-Thread gesendet. Wenn beispielsweise ein Prozess (A) einen synchronen Aufruf von einem Hwbinder-Thread an Prozess (B) durchführt und Prozess (B) dann einen synchronen Rückruf an Prozess (A) durchführt, wird der Aufruf auf dem ursprünglichen Hwbinder-Thread ausgeführt in (A), der beim ursprünglichen Anruf blockiert ist. Durch diese Optimierung ist es möglich, einen einzelnen Thread-Server zu haben, der verschachtelte Aufrufe verarbeiten kann, sie erstreckt sich jedoch nicht auf Fälle, in denen die Aufrufe eine andere Folge von IPC-Aufrufen durchlaufen. Wenn beispielsweise Prozess (B) einen binder/vndbinder-Aufruf durchgeführt hat, der einen Prozess (C) aufruft und dann Prozess (C) zurück zu (A) aufruft, kann er nicht im ursprünglichen Thread in (A) bedient werden.

Server-Threading-Modell

Mit Ausnahme des Passthrough-Modus befinden sich Serverimplementierungen von HIDL-Schnittstellen in einem anderen Prozess als der Client und benötigen einen oder mehrere Threads, die auf eingehende Methodenaufrufe warten. Diese Threads sind der Threadpool des Servers. Der Server kann entscheiden, wie viele Threads er in seinem Threadpool ausführen möchte, und kann eine Threadpoolgröße von eins verwenden, um alle Aufrufe an seinen Schnittstellen zu serialisieren. Wenn der Server mehr als einen Thread im Threadpool hat, kann er gleichzeitig eingehende Aufrufe auf jeder seiner Schnittstellen empfangen (in C++ bedeutet dies, dass gemeinsam genutzte Daten sorgfältig gesperrt werden müssen).

Einwegaufrufe in dieselbe Schnittstelle werden serialisiert. Wenn ein Multithread-Client method1 und method2 auf der Schnittstelle IFoo und method3 auf der Schnittstelle IBar aufruft, werden method1 und method2 immer serialisiert, method3 kann jedoch parallel zu method1 und method2 ausgeführt werden.

Ein einzelner Client-Ausführungsthread kann auf zwei Arten eine gleichzeitige Ausführung auf einem Server mit mehreren Threads bewirken:

  • oneway werden nicht blockiert. Wenn ein oneway Anruf ausgeführt wird und dann ein nicht oneway Anruf aufgerufen wird, kann der Server den oneway Anruf und den nicht oneway Anruf gleichzeitig ausführen.
  • Servermethoden, die Daten mit synchronen Rückrufen zurückgeben, können den Client entsperren, sobald der Rückruf vom Server aufgerufen wird.

Bei der zweiten Möglichkeit kann jeder Code in der Serverfunktion, der nach dem Aufruf des Rückrufs ausgeführt wird, gleichzeitig ausgeführt werden, wobei der Server nachfolgende Aufrufe vom Client verarbeitet. Dazu gehören Code in der Serverfunktion und automatische Destruktoren, die am Ende der Funktion ausgeführt werden. Wenn der Server über mehr als einen Thread in seinem Threadpool verfügt, treten Parallelitätsprobleme auf, selbst wenn Aufrufe nur von einem einzigen Client-Thread eingehen. (Wenn eine von einem Prozess bereitgestellte HAL mehrere Threads benötigt, verfügen alle HALs über mehrere Threads, da der Threadpool pro Prozess gemeinsam genutzt wird.)

Sobald der Server den bereitgestellten Rückruf aufruft, kann der Transport den implementierten Rückruf auf dem Client aufrufen und den Client entsperren. Der Client fährt parallel zu dem fort, was die Serverimplementierung tut, nachdem sie den Rückruf aufgerufen hat (einschließlich der Ausführung von Destruktoren). Code in der Serverfunktion blockiert nach dem Rückruf nicht mehr den Client (solange der Server-Threadpool über genügend Threads verfügt, um eingehende Aufrufe zu verarbeiten), kann aber gleichzeitig mit zukünftigen Aufrufen vom Client ausgeführt werden (es sei denn, der Server-Threadpool verfügt nur über einen Thread). ).

Zusätzlich zu synchronen Rückrufen können oneway von einem Single-Threaded-Client gleichzeitig von einem Server mit mehreren Threads in seinem Threadpool verarbeitet werden, jedoch nur, wenn diese oneway auf verschiedenen Schnittstellen ausgeführt werden. oneway auf derselben Schnittstelle werden immer serialisiert.

Hinweis: Wir empfehlen dringend, dass Serverfunktionen zurückkehren, sobald sie die Rückruffunktion aufgerufen haben.

Zum Beispiel (in C++):

Return<void> someMethod(someMethod_cb _cb) {
    // Do some processing, then call callback with return data
    hidl_vec<uint32_t> vec = ...
    _cb(vec);
    // At this point, the client's callback will be called,
    // and the client will resume execution.
    ...
    return Void(); // is basically a no-op
};

Client-Threading-Modell

Das Threading-Modell auf dem Client unterscheidet zwischen nicht blockierenden Aufrufen (Funktionen, die mit dem Schlüsselwort oneway gekennzeichnet sind) und blockierenden Aufrufen (Funktionen, für die das Schlüsselwort oneway nicht angegeben ist).

Anrufe blockieren

Bei blockierenden Anrufen blockiert der Client, bis eines der folgenden Ereignisse eintritt:

  • Transportfehler tritt auf; Das Return Objekt enthält einen Fehlerstatus, der mit Return::isOk() abgerufen werden kann.
  • Die Serverimplementierung ruft den Rückruf auf (falls vorhanden).
  • Die Serverimplementierung gibt einen Wert zurück (wenn kein Rückrufparameter vorhanden war).

Im Erfolgsfall wird die Callback-Funktion, die der Client als Argument übergibt, immer vom Server aufgerufen, bevor die Funktion selbst zurückkehrt. Der Rückruf wird auf demselben Thread ausgeführt, auf dem der Funktionsaufruf erfolgt. Daher müssen Implementierer beim Halten von Sperren während Funktionsaufrufen vorsichtig sein (und sie nach Möglichkeit ganz vermeiden). Eine Funktion ohne eine generates -Anweisung oder ein oneway -Schlüsselwort blockiert immer noch; Der Client blockiert, bis der Server ein Return<void> -Objekt zurückgibt.

Oneway-Anrufe

Wenn eine Funktion als oneway markiert ist, kehrt der Client sofort zurück und wartet nicht darauf, dass der Server seinen Funktionsaufruf abschließt. Oberflächlich betrachtet (und insgesamt) bedeutet dies, dass der Funktionsaufruf die Hälfte der Zeit benötigt, da er die Hälfte des Codes ausführt. Beim Schreiben von Implementierungen, die leistungsempfindlich sind, hat dies jedoch einige Auswirkungen auf die Planung. Normalerweise führt die Verwendung eines Einweganrufs dazu, dass der Anrufer weiterhin eingeplant wird, wohingegen die Verwendung eines normalen synchronen Anrufs dazu führt, dass der Planer sofort vom Anrufer zum Angerufenenprozess wechselt. Dies ist eine Leistungsoptimierung im Bindemittel. Für Dienste, bei denen der Einwegaufruf im Zielprozess mit hoher Priorität ausgeführt werden muss, kann die Planungsrichtlinie des empfangenden Dienstes geändert werden. In C++ stellt die Verwendung der Methode setMinSchedulerPolicy von libhidltransport mit den in sched.h definierten Scheduler-Prioritäten und -Richtlinien sicher, dass alle Aufrufe des Dienstes mindestens mit der festgelegten Planungsrichtlinie und -priorität ausgeführt werden.