标记为 oneway
的方法不会阻止。对于未标记为 oneway
的方法,客户端的方法调用会一直阻止,直到服务器完成执行或调用同步回调(以先到者为准)。服务器方法实现最多可以调用一个同步回调;额外的回调调用将被丢弃并记录为错误。如果某个方法应该通过回调返回值,但未调用其回调,则这将被记录为错误,并作为传输错误报告给客户端。
直通模式下的线程
在直通模式下,大多数调用是同步的。但是,为了保留 oneway
调用不阻止客户端的预期行为,将为每个进程创建一个线程。有关详细信息,请参阅 HIDL 概览。
Binder 化 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 接口的服务器实现在与客户端不同的进程中运行,并且需要一个或多个线程来等待传入的方法调用。这些线程是服务器的线程池;服务器可以决定其线程池中运行的线程数量,并且可以使用大小为 1 的线程池来序列化其接口上的所有调用。如果服务器的线程池中有多个线程,则它可以接收对其任何接口的并发传入调用(在 C++ 中,这意味着必须仔细锁定共享数据)。
进入同一接口的单向调用是序列化的。如果多线程客户端在接口 IFoo
上调用 method1
和 method2
,并在接口 IBar
上调用 method3
,则 method1
和 method2
始终是序列化的,但 method3
可以与 method1
和 method2
并行运行。
单个客户端执行线程可以通过两种方式导致在具有多个线程的服务器上并发执行
oneway
调用不会阻塞。如果执行oneway
调用,然后调用非oneway
调用,则服务器可以同时执行oneway
调用和非oneway
调用。- 通过同步回调将数据传递回的服务器方法可以在从服务器调用回调后立即解除客户端阻塞。
对于第二种方式,服务器函数中在调用回调后执行的任何代码都可以并发执行,服务器同时处理来自客户端的后续调用。这包括服务器函数中的代码以及在函数末尾执行的自动析构函数。如果服务器的线程池中有多个线程,即使调用仅来自单个客户端线程,也会出现并发问题。(如果进程提供的任何 HAL 需要多个线程,则所有 HAL 都有多个线程,因为线程池是按进程共享的。)
一旦服务器调用提供的回调,传输就可以在客户端上调用已实现的回调并解除客户端阻塞。客户端与服务器在调用回调后执行的任何操作(可能包括运行析构函数)并行进行。回调之后的服务器函数中的代码不再阻塞客户端(只要服务器线程池有足够的线程来处理传入的调用),但可能会与来自客户端的未来调用并发执行(除非服务器线程池只有一个线程)。
除了同步回调之外,来自单线程客户端的 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 is called, // and the client resumes execution. ... return Void(); // is basically a no-op };
客户端线程模型
客户端上的线程模型在非阻塞调用(标记有 oneway
关键字的函数)和阻塞调用(未指定 oneway
关键字的函数)之间有所不同。
阻塞调用
对于阻塞调用,客户端会阻塞,直到发生以下情况之一
- 发生传输错误;
Return
对象包含可以使用Return::isOk()
检索的错误状态。 - 服务器实现调用回调(如果存在)。
- 服务器实现返回值(如果没有回调参数)。
在成功的情况下,客户端作为参数传递的回调函数始终在函数本身返回之前由服务器调用。回调在进行函数调用的同一线程上执行,因此实现者必须小心在函数调用期间持有锁(并尽可能完全避免使用锁)。没有 generates
语句或 oneway
关键字的函数仍然是阻塞的;客户端会阻塞,直到服务器返回 Return<void>
对象。
单向调用
当函数标记为 oneway
时,客户端会立即返回,并且不等待服务器完成其函数调用调用。从表面上看(总体而言),这意味着函数调用花费的时间只有一半,因为它执行的代码只有一半,但是当编写对性能敏感的实现时,这会产生一些调度影响。通常,使用单向调用会导致调用方继续被调度,而使用正常的同步调用会导致调度程序立即从调用方转移到被调用方进程。这是 binder 中的性能优化。对于必须以高优先级在目标进程中执行单向调用的服务,可以更改接收服务的调度策略。在 C++ 中,使用 libhidltransport
的方法 setMinSchedulerPolicy
以及在 sched.h
中定义的调度优先级和策略,可确保对服务的所有调用至少以设置的调度策略和优先级运行。