Ручка нитей

Поточная модель Binder разработана для упрощения локальных вызовов функций, даже если эти вызовы относятся к удалённому процессу. В частности, любой процесс, размещающий узел, должен иметь пул из одного или нескольких потоков Binder для обработки транзакций к узлам, размещенным в этом процессе.

Синхронные и асинхронные транзакции

Binder поддерживает синхронные и асинхронные транзакции. В следующих разделах объясняется, как выполняется каждый тип транзакции.

Синхронные транзакции

Синхронные транзакции блокируются до тех пор, пока они не будут выполнены на узле и не будет получен ответ на эту транзакцию вызывающей стороной. На следующем рисунке показано, как выполняется синхронная транзакция:

Синхронная транзакция.

Рисунок 1. Синхронная транзакция.

Чтобы выполнить синхронную транзакцию, binder делает следующее:

  1. Потоки в целевом пуле потоков (T2 и T3) обращаются к драйверу ядра для ожидания входящей работы.
  2. Ядро получает новую транзакцию и активирует поток (T2) в целевом процессе для обработки транзакции.
  3. Вызывающий поток (T1) блокируется и ждет ответа.
  4. Целевой процесс выполняет транзакцию и возвращает ответ.
  5. Поток в целевом процессе (T2) обращается к драйверу ядра, чтобы дождаться новой работы.

Асинхронные транзакции

Асинхронные транзакции не блокируются до завершения; вызывающий поток разблокируется сразу после передачи транзакции в ядро. На следующем рисунке показано, как выполняется асинхронная транзакция:

Асинхронная транзакция.

Рисунок 2. Асинхронная транзакция.

  1. Потоки в целевом пуле потоков (T2 и T3) обращаются к драйверу ядра для ожидания входящей работы.
  2. Ядро получает новую транзакцию и активирует поток (T2) в целевом процессе для обработки транзакции.
  3. Вызывающий поток (T1) продолжает выполнение.
  4. Целевой процесс выполняет транзакцию и возвращает ответ.
  5. Поток в целевом процессе (T2) обращается к драйверу ядра, чтобы дождаться новой работы.

Определите синхронную или асинхронную функцию

Функции, отмеченные в AIDL-файле как oneway , являются асинхронными. Например:

oneway void someCall();

Если функция не помечена как oneway , то это синхронная функция, даже если функция возвращает void .

Сериализация асинхронных транзакций

Binder сериализует асинхронные транзакции с любого узла. На следующем рисунке показано, как Binder сериализует асинхронные транзакции:

Сериализация асинхронных транзакций.

Рисунок 3. Сериализация асинхронных транзакций.

  1. Потоки в целевом пуле потоков (B1 и B2) обращаются к драйверу ядра для ожидания входящей работы.
  2. Две транзакции (T1 и T2) на одном узле (N1) отправляются в ядро.
  3. Ядро получает новые транзакции и, поскольку они поступают с одного и того же узла (N1), сериализует их.
  4. Другая транзакция на другом узле (N2) отправляется в ядро.
  5. Ядро получает третью транзакцию и активирует поток (B2) в целевом процессе для обработки транзакции.
  6. Целевые процессы выполняют каждую транзакцию и возвращают ответ.

Вложенные транзакции

Синхронные транзакции могут быть вложенными; поток, обрабатывающий транзакцию, может инициировать новую транзакцию. Вложенная транзакция может быть выполнена в другом процессе или в том же процессе, из которого получена текущая транзакция. Такое поведение имитирует вызовы локальных функций. Например, предположим, что у вас есть функция со вложенными функциями:

def outer_function(x):
    def inner_function(y):
        def inner_inner_function(z):

Если это локальные вызовы, они выполняются в том же потоке. В частности, если вызывающий inner_function процесс также является процессом, содержащим узел, реализующий inner_inner_function , вызов inner_inner_function выполняется в том же потоке.

На следующем рисунке показано, как Binder обрабатывает вложенные транзакции:

Вложенные транзакции.

Рисунок 4. Вложенные транзакции.

  1. Поток A1 запрашивает запуск foo() .
  2. В рамках этого запроса поток B1 запускает функцию bar() , которую A запускает в том же потоке A1.

На следующем рисунке показано выполнение потока, если узел, реализующий bar() находится в другом процессе:

Вложенные транзакции в разных процессах.

Рисунок 5. Вложенные транзакции в разных процессах.

  1. Поток A1 запрашивает запуск foo() .
  2. В рамках этого запроса поток B1 запускает функцию bar() , которая выполняется в другом потоке C1.

На следующем рисунке показано, как поток повторно использует один и тот же процесс в любом месте цепочки транзакций:

Вложенные транзакции повторно используют поток.

Рисунок 6. Вложенные транзакции, повторно использующие поток.

  1. Процесс А вызывает процесс Б.
  2. Процесс B вызывает процесс C.
  3. Затем процесс C осуществляет обратный вызов в процессе A, и ядро повторно использует поток A1 в процессе A, который является частью цепочки транзакций.

Для асинхронных транзакций вложенность не играет роли; клиент не ждёт результата асинхронной транзакции, поэтому вложенности нет. Если обработчик асинхронной транзакции обращается к процессу, инициировавшем эту асинхронную транзакцию, эта транзакция может быть обработана в любом свободном потоке этого процесса.

Избегайте тупиков

На следующем рисунке показана типичная взаимоблокировка:

Обычный тупик.

Рисунок 7. Распространенный тупик.

  1. Процесс A берет мьютекс MA и делает вызов связывателя (T1) к процессу B, который также пытается взять мьютекс MB.
  2. Одновременно процесс B берет мьютекс MB и делает вызов связывателя (T2) к процессу A, который пытается взять мьютекс MA.

Если эти транзакции перекрываются, каждая транзакция потенциально может занять мьютекс в своем процессе, ожидая, пока другой процесс освободит мьютекс, что приведет к взаимоблокировке.

Чтобы избежать взаимоблокировок при использовании подмены, не удерживайте никаких блокировок при вызове подмены.

Правила упорядочивания блокировок и взаимоблокировки

В рамках одной среды выполнения взаимоблокировок часто удается избежать с помощью правила упорядочивания блокировок. Однако при вызовах между процессами и между кодовыми базами, особенно при обновлении кода, поддерживать и координировать правило упорядочивания невозможно.

Одиночные мьютексы и взаимоблокировки

При вложенных транзакциях процесс B может напрямую обратиться к тому же потоку процесса A, удерживающего мьютекс. Таким образом, из-за непредвиденной рекурсии всё ещё возможно возникновение взаимоблокировки с одним мьютексом.

Синхронные вызовы и взаимоблокировки

Хотя асинхронные вызовы связывателя не блокируются до завершения, следует избегать удержания блокировки для асинхронных вызовов. Если вы удерживаете блокировку, могут возникнуть проблемы с блокировкой, если односторонний вызов случайно изменится на синхронный.

Одинарная нить переплета и тупики

Транзакционная модель Binder допускает повторный вход, поэтому даже если у процесса есть один поток связывания, блокировка всё равно необходима. Например, предположим, что вы итерируете по списку в однопоточном процессе A. Для каждого элемента списка вы создаёте исходящую транзакцию связывания. Если реализация вызываемой функции создаёт новую транзакцию связывания к узлу, размещённому в процессе A, эта транзакция обрабатывается в том же потоке, который итерировал список. Если реализация этой транзакции изменяет тот же список, при дальнейшем итерировании по списку могут возникнуть проблемы.

Настроить размер пула потоков

Если у сервиса несколько клиентов, добавление потоков в пул потоков может снизить конкуренцию и обеспечить параллельное обслуживание большего количества вызовов. После корректной реализации параллелизма можно добавить дополнительные потоки. Проблема, возникающая при добавлении дополнительных потоков, может заключаться в том, что некоторые потоки могут не использоваться во время неактивных рабочих нагрузок.

Потоки создаются по требованию до достижения настроенного максимума. После создания потока связующего процесса он остается активным до тех пор, пока не завершится процесс, в котором он размещен.

В библиотеке libbinder установлено значение по умолчанию — 15 потоков. Чтобы изменить это значение, используйте setThreadPoolMaxThreadCount :

using ::android::ProcessState;
ProcessState::self()->setThreadPoolMaxThreadCount(size_t maxThreads);