スレッドモデル

oneway が付いたメソッドはブロックを実行しません。oneway が付いていないメソッドの場合、クライアントがメソッドを呼び出すと、サーバーが実行を完了するか、同期コールバックを呼び出すまでブロックが実行されます。サーバーのメソッド実装では、同期コールバックを 1 回だけ呼び出すことができます。追加のコールバック呼び出しは破棄されてエラーとして記録されます。コールバックを介して値を返すことが想定されているメソッドがコールバックを呼び出さない場合、ログにエラーが記録され、トランスポート エラーとしてクライアントに報告されます。

パススルー モードのスレッド

パススルー モードでは、ほとんどの呼び出しが同期します。ただし、oneway 呼び出しがクライアントをブロックしないよう、プロセスごとにスレッドが作成されます。詳しくは、HIDL の概要をご覧ください。

バインダー化された HAL のスレッド

着信 RPC 呼び出し(HAL から HAL ユーザーへの非同期コールバックを含む)と終了通知に対処できるように、スレッドプールは HIDL を使用する各プロセスに関連付けられています。1 つのプロセスが複数の HIDL インターフェースや終了通知ハンドラを実装している場合、それらすべてでスレッドプールが共有されます。プロセスは、クライアントから着信メソッド呼び出しを受け取ると、スレッドプールから空きスレッドを選択し、そのスレッドで呼び出しを実行します。使用できる空きスレッドがない場合は、いずれかのスレッドが使用可能になるまでブロックを実行します。

サーバーにあるスレッドが 1 つのみの場合は、サーバーへの呼び出しが順番に完了します。スレッドが複数あるサーバーは、クライアントにスレッドが 1 つしかなくても順不同で呼び出しを完了できます。ただし、特定のインターフェース オブジェクトについては、oneway 呼び出しの順序が保証されます(サーバー スレッドモデルをご覧ください)。複数のインターフェースをホストするマルチスレッド サーバーの場合、異なるインターフェースへの oneway 呼び出しは、相互または他のブロッキング呼び出しと同時に処理できます。

複数のネスト呼び出しが、同じ hwbinder スレッドで送信されます。たとえば、プロセス(A)が hwbinder スレッドからプロセス(B)に同期呼び出しを行い、プロセス(B)がプロセス(A)に同期コールバックを行うと、元の呼び出しでブロックされている(A)の元の hwbinder スレッドで呼び出しが実行されます。この最適化により、1 台のスレッド サーバーでネスト呼び出しを処理できるようになりますが、IPC 呼び出しの別のシーケンスを経由する呼び出しは対象外です。たとえば、プロセス(B)がプロセス(C)に binder / vndbinder 呼び出しを行った後にプロセス(C)が(A)にコールバックしても、(A)の元のスレッドでは処理できません。

サーバー スレッドモデル

パススルー モード以外では、HIDL インターフェースのサーバー実装はクライアントとは別のプロセスで実行され、着信メソッド呼び出しを待機する 1 つ以上のスレッドを必要とします。これらのスレッドはサーバーのスレッドプールです。サーバーは、スレッドプールで実行するスレッドの数を決定し、いずれかのスレッドプール サイズを使用してインターフェース上のすべての呼び出しをシリアル化できます。サーバーのスレッドプールに複数のスレッドがある場合、いずれかのインターフェースで同時着信呼び出しを受信できます(つまり、C++ では共有データを慎重にロックする必要があります)。

同じインターフェースへの oneway 呼び出しはシリアル化されます。マルチスレッド クライアントがインターフェース IFoomethod1method2、インターフェース IBarmethod3 を呼び出した場合、method1method2 は常にシリアル化されますが、method3method1 および method2 と並列実行される可能性があります。

1 つのクライアント実行スレッドが、次の 2 つの方法で、スレッドが複数あるサーバーでの同時実行を引き起こす場合があります。

  • oneway 呼び出しはブロックを実行しません。oneway 呼び出しが実行され、次に oneway 以外が呼び出されると、サーバーは oneway 呼び出しと oneway 以外の呼び出しを同時に実行する場合があります。
  • 同期コールバックを使用してデータを返すサーバー メソッドでは、コールバックがサーバーから呼び出されるとすぐにクライアントのブロックを解除できます。

2 番目の方法では、コールバックが呼び出された後に実行されるサーバー関数のコードが、クライアントからの後続の呼び出しをサーバーが処理するのと同時に実行される場合があります。これには、サーバー関数のコードと、関数の最後に実行される自動デストラクタが含まれます。サーバーのスレッドプールに複数のスレッドがある場合は、1 つのクライアント スレッドからのみ呼び出しを着信しても、同時実行の問題が発生します(プロセスによって提供される HAL に複数のスレッドが必要な場合、スレッドプールはプロセスごとに共有されるため、すべての HAL に複数のスレッドが存在することになります)。

サーバーが指定のコールバックを呼び出すとすぐに、トランスポートはクライアントで実装されたコールバックを呼び出して、クライアントのブロックを解除できます。クライアントは、コールバックを呼び出した後のサーバー実装による処理と並行して処理を行います(これには、デストラクタの実行が含まれます)。サーバーのスレッドプールに着信呼び出しを処理できる十分なスレッドがあれば、コールバック後のサーバー関数のコードによってクライアントのブロックが解除されますが、クライアントからの以降の呼び出しが同時に実行される可能性があります(サーバーのスレッドプールにスレッドが 1 つしかない場合を除きます)。

同期コールバックに加えて、シングルスレッド クライアントからの oneway 呼び出しは、スレッドプールに複数のスレッドがあるサーバーによって同時に処理されることがあります。ただしこれは、oneway 呼び出しが異なるインターフェースで実行される場合のみです。同じインターフェース上の oneway 呼び出しは常にシリアル化されます。

注: コールバック関数を呼び出した直後にサーバー関数を返すことを強くおすすめします。

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
};

クライアント スレッドモデル

クライアントのスレッドモデルは、非ブロッキング呼び出し(oneway キーワードが付いた関数)とブロッキング呼び出し(oneway キーワードが指定されていない関数)で異なります。

ブロッキング呼び出し

ブロッキング呼び出しの場合、クライアントは次のいずれかが発生するまでブロックを実行します。

  • トランスポート エラーが発生する(Return オブジェクトに Return::isOk() で取得できるエラー状態が含まれている)。
  • サーバー実装がコールバックを呼び出す(該当する場合)。
  • サーバー実装が値を返す(コールバック パラメータがない場合)。

成功した場合にクライアントが引数として渡すコールバック関数は、関数自体が返す前に常にサーバーによって呼び出されます。コールバックは関数呼び出しと同じスレッドで実行されるため、関数呼び出し中のロックの保持に注意する(可能であれば完全に回避する)必要があります。generates ステートメントまたは oneway キーワードがない関数は、引き続きブロックを実行します。クライアントはサーバーが Return<void> オブジェクトを返すまでブロックを実行します。

oneway 呼び出し

関数に oneway が付いている場合、クライアントはサーバーが関数呼び出しを完了するのを待たずにすぐに返します。実行されるのがコードの半分であるため、表面的にも総体的にも、これは関数呼び出しに要する時間が半分であることを意味します。なお、パフォーマンス重視の実装を記述する場合、これはスケジュール設定に影響します。通常、oneway 呼び出しを使用すると、呼び出し元はスケジュール設定されたままになりますが、通常の同期呼び出しを使用すると、スケジューラが呼び出し元から呼び出し先プロセスに直ちに転送されます。これは、バインダーでのパフォーマンスの最適化です。優先度の高いターゲット プロセスで oneway 呼び出しを実行する必要があるサービスの場合、受信側サービスのスケジューリング ポリシーを変更できます。C++ で libhidltransport のメソッド setMinSchedulerPolicy と、sched.h で定義されたスケジューラの優先度およびポリシーを使用すると、サービスへのすべての呼び出しが、少なくとも設定されたスケジュール ポリシーと優先度で実行されます。