线程模型

标记为 oneway 的方法不会阻塞。对于未标记为 oneway 的方法,在服务器完成执行任务或调用同步回调(以先发生者为准)之前,客户端的方法调用将一直处于阻塞状态。服务器方法实现最多可以调用一个同步回调;多出的回调调用会被舍弃并记录为错误。如果方法应通过回调返回值,但未调用其回调,系统会将这种情况记录为错误,并作为传输错误报告给客户端。

直通模式下的线程

在直通模式下,大多数调用都是同步的。不过,为确保 oneway 调用不会阻塞客户端这一预期行为,系统会分别为每个进程创建线程。要了解详情,请参阅 HIDL 概览

绑定式 HAL 中的线程

为了处理传入的 RPC 调用(包括从 HAL 到 HAL 用户的异步回调)和终止通知,系统会为使用 HIDL 的每个进程关联一个线程池。如果单个进程实现了多个 HIDL 接口和/或终止通知处理程序,则所有这些接口和/或处理程序会共享其线程池。当进程接收从客户端传入的方法调用时,它会从线程池中选择一个空闲线程,并在该线程上执行调用。如果没有空闲的线程,它将会阻塞,直到有可用线程为止。

如果服务器只有一个线程,则传入服务器的调用将按顺序完成。具有多个线程的服务器可以不按顺序完成调用,即使客户端只有一个线程也是如此。不过,对于特定的接口对象,oneway 调用会保证按顺序进行(请参阅服务器线程模型)。对于托管了多个界面的多线程服务器,对不同界面的多项 oneway 调用可能会并行处理,也可能会与其他阻塞调用并行处理。

系统会在同一个 hwbinder 线程中发送多个嵌套调用。例如,如果进程 (A) 通过 hwbinder 线程对进程 (B) 进行同步调用,然后进程 (B) 对进程 (A) 进行同步回调,则系统会在 (A) 中的原始 hwbinder 线程(在原始调用中已被屏蔽)上执行该调用。这种优化使单个线程服务器能够处理嵌套调用,但是对于需要在其他 IPC 调用序列中传输调用的情况,这种优化并不适用。例如,如果进程 (B) 进行了 binder/vndbinder 调用,并在此过程中调用了进程 (C),然后进程 (C) 回调进程 (A),则系统无法在进程 (A) 中的原始线程上处理该调用。

服务器线程模型

(直通模式除外)HIDL 接口的服务器实现位于不同于客户端的进程中,并且需要一个或多个线程等待传入的方法调用。这些线程构成服务器的线程池;服务器可以决定它希望在其线程池中运行多少线程,并且可以利用一个线程大小的线程池来按顺序处理其接口上的所有调用。如果服务器的线程池中有多个线程,则服务器可以在其任何接口上接收同时传入的调用(在 C++ 中,这意味着必须小心锁定共享数据)。

传入同一接口的单向调用会按顺序进行处理。如果多线程客户端在接口 IFoo 上调用 method1method2,并在接口 IBar 上调用 method3,则 method1method2 将始终按顺序运行,但 method3 可以与 method1method2 并行运行。

单一客户端执行线程可能会通过以下两种方式在具有多个线程的服务器上引发并行运行:

  • oneway 调用不会阻塞。如果执行 oneway 调用,然后调用非 oneway,则服务器可以同时执行 oneway 调用和非 oneway 调用。
  • 当系统从服务器调用回调时,通过同步回调传回数据的服务器方法可以立即解除对客户端的阻塞。

对于第二种方式,在调用回调之后执行的服务器函数中的任何代码都可以并行运行,同时服务器会处理来自客户端的后续调用。这包括服务器函数中的代码,以及在函数结束时执行的自动析构函数中的代码。如果服务器的线程池中有多个线程,那么即使仅从一个单一客户端线程传入调用,也会出现并行处理问题。(如果一个进程提供的任意 HAL 需要多个线程,则所有 HAL 都将具有多个线程,因为线程池是按进程共享的。)

当服务器调用所提供的回调时,transport 可以立即调用客户端上已实现的回调,并解除对客户端的阻塞。客户端会继续与服务器实现在调用回调之后所执行的任何任务(可能包括正在运行的析构函数)并行运行。回调后,只要服务器线程池中有足够多的线程来处理传入的调用,服务器函数中的代码就不会再阻塞客户端,但可以与来自客户端的未来调用并行执行(除非服务器线程池中只有一个线程)。

除了同步回调外,来自单线程客户端的 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,则客户端会立即返回,而不会等待服务器完成其函数调用。从表面(整体)上看,这意味着函数调用只用了一半的时间,因为它执行了一半的代码,但是当编写性能敏感型实现时,这会带来一些调度方面的影响。通常,使用单向调用会导致调用程序继续被调度,而使用正常的同步调用会使调度程序立即从调用程序转移到被调用程序进程。这就是 binder 中的性能优化。对于必须在具有高优先级的目标进程中执行单向调用的服务,可以更改接收服务的调度策略。在 C++ 中,使用 libhidltransportsetMinSchedulerPolicy 方法,并在 sched.h 中定义调度程序优先级和策略可确保所有对服务的调用至少以设置的调度策略和优先级运行。