스레드 처리

바인더의 스레딩 모델은 이러한 호출이 원격 프로세스로 이루어질 수 있더라도 로컬 함수 호출을 용이하게 하도록 설계되었습니다. 특히 노드를 호스팅하는 프로세스에는 해당 프로세스에서 호스팅되는 노드로의 트랜잭션을 처리하는 바인더 스레드 풀이 하나 이상 있어야 합니다.

동기 및 비동기 트랜잭션

바인더는 동기 및 비동기 트랜잭션을 지원합니다. 다음 섹션에서는 각 거래 유형이 실행되는 방식을 설명합니다.

동기 트랜잭션

동기 트랜잭션은 노드에서 실행되고 해당 트랜잭션에 대한 응답이 호출자에게 수신될 때까지 차단됩니다. 다음 그림은 동기 트랜잭션이 실행되는 방식을 보여줍니다.

동기 트랜잭션입니다.

그림 1. 동기 트랜잭션입니다.

동기 트랜잭션을 실행하기 위해 바인더는 다음을 실행합니다.

  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를 반환하더라도 동기 함수입니다.

비동기 트랜잭션의 직렬화

바인더는 단일 노드에서 비동기 트랜잭션을 직렬화합니다. 다음 그림은 바인더가 비동기 트랜잭션을 직렬화하는 방법을 보여줍니다.

비동기 트랜잭션의 직렬화

그림 3. 비동기 트랜잭션의 직렬화

  1. 타겟 스레드 풀 (B1 및 B2)의 스레드는 커널 드라이버를 호출하여 수신되는 작업을 기다립니다.
  2. 동일한 노드 (N1)의 두 트랜잭션 (T1 및 T2)이 커널로 전송됩니다.
  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 호출은 동일한 스레드에서 실행됩니다.

다음 그림은 바인더가 중첩된 트랜잭션을 처리하는 방법을 보여줍니다.

중첩된 트랜잭션

그림 4. 중첩된 트랜잭션

  1. 스레드 A1이 foo() 실행을 요청합니다.
  2. 이 요청의 일부로 스레드 B1은 동일한 스레드 A1에서 A가 실행되는 bar()를 실행합니다.

다음 그림은 bar()를 구현하는 노드가 다른 프로세스에 있는 경우의 스레드 실행을 보여줍니다.

서로 다른 프로세스의 중첩된 트랜잭션

그림 5. 서로 다른 프로세스의 중첩된 트랜잭션

  1. 스레드 A1이 foo() 실행을 요청합니다.
  2. 이 요청의 일부로 스레드 B1은 다른 스레드 C1에서 실행되는 bar()를 실행합니다.

다음 그림은 스레드가 트랜잭션 체인의 어디에서나 동일한 프로세스를 재사용하는 방법을 보여줍니다.

스레드를 재사용하는 중첩된 트랜잭션

그림 6. 스레드를 재사용하는 중첩된 트랜잭션

  1. 프로세스 A가 프로세스 B를 호출합니다.
  2. 프로세스 B가 프로세스 C를 호출합니다.
  3. 그런 다음 프로세스 C가 프로세스 A로 다시 호출하고 커널은 트랜잭션 체인의 일부인 프로세스 A의 스레드 A1을 재사용합니다.

비동기 트랜잭션의 경우 중첩이 역할을 하지 않습니다. 클라이언트는 비동기 트랜잭션의 결과를 기다리지 않으므로 중첩이 없습니다. 비동기 트랜잭션의 핸들러가 해당 비동기 트랜잭션을 실행한 프로세스로 호출하는 경우 해당 트랜잭션은 해당 프로세스의 여유 스레드에서 처리될 수 있습니다.

교착 상태 방지

다음 이미지는 일반적인 교착 상태를 보여줍니다.

일반적인 교착 상태입니다.

그림 7. 일반적인 교착 상태입니다.

  1. 프로세스 A가 뮤텍스 MA를 가져와 뮤텍스 MB를 가져오려고 시도하는 프로세스 B에 바인더 호출 (T1)을 합니다.
  2. 동시에 프로세스 B는 뮤텍스 MB를 가져와 뮤텍스 MA를 가져오려고 시도하는 프로세스 A에 바인더 호출 (T2)을 실행합니다.

이러한 트랜잭션이 겹치면 각 트랜잭션이 다른 프로세스가 뮤텍스를 해제할 때까지 기다리는 동안 프로세스에서 뮤텍스를 가져와 교착 상태가 발생할 수 있습니다.

바인더를 사용하는 동안 교착 상태를 방지하려면 바인더 호출 시 잠금을 유지하지 마세요.

잠금 순서 지정 규칙 및 교착 상태

단일 실행 환경 내에서는 잠금 순서 지정 규칙을 사용하여 교착 상태를 방지하는 경우가 많습니다. 하지만 프로세스 간 및 코드베이스 간에 호출을 할 때는 특히 코드가 업데이트될 때 순서 지정 규칙을 유지하고 조정할 수 없습니다.

단일 뮤텍스 및 교착 상태

중첩된 트랜잭션을 사용하면 프로세스 B가 뮤텍스를 보유하는 프로세스 A의 동일한 스레드로 직접 다시 호출할 수 있습니다. 따라서 예기치 않은 재귀로 인해 단일 뮤텍스로 교착 상태가 발생할 수 있습니다.

동기 호출 및 교착 상태

비동기 바인더 호출은 완료를 위해 차단되지 않지만 비동기 호출을 위해 잠금을 유지하는 것도 피해야 합니다. 잠금을 보유한 경우 단방향 호출이 실수로 동기 호출로 변경되면 잠금 문제가 발생할 수 있습니다.

단일 바인더 스레드 및 교착 상태

바인더의 트랜잭션 모델은 재진입을 허용하므로 프로세스에 단일 바인더 스레드가 있더라도 잠금이 필요합니다. 예를 들어 단일 스레드 프로세스 A에서 목록을 반복한다고 가정해 보겠습니다. 목록의 각 항목에 대해 바인더 트랜잭션을 전송합니다. 호출하는 함수의 구현이 프로세스 A에서 호스팅되는 노드로 새 바인더 트랜잭션을 만드는 경우 해당 트랜잭션은 목록을 반복하는 것과 동일한 스레드에서 처리됩니다. 해당 트랜잭션의 구현이 동일한 목록을 수정하는 경우 나중에 목록을 계속 반복할 때 문제가 발생할 수 있습니다.

스레드 풀 크기 구성

서비스에 클라이언트가 여러 개 있는 경우 스레드 풀에 스레드를 추가하면 경합을 줄이고 더 많은 호출을 병렬로 처리할 수 있습니다. 동시성을 올바르게 처리한 후 스레드를 더 추가할 수 있습니다. 일부 스레드가 조용한 워크로드 중에 사용되지 않을 수 있는 스레드를 추가하여 발생할 수 있는 문제입니다.

스레드는 바인더 스레드가 생성된 후 이를 호스팅하는 프로세스가 종료될 때까지 활성 상태로 유지됩니다.

libbinder 라이브러리의 기본값은 15개 스레드입니다. setThreadPoolMaxThreadCount을 사용하여 이 값을 변경합니다.

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