Menangani thread

Model threading Binder dirancang untuk memfasilitasi panggilan fungsi lokal meskipun panggilan tersebut mungkin ditujukan ke proses jarak jauh. Secara khusus, setiap proses yang menghosting node harus memiliki kumpulan satu atau beberapa thread binder untuk menangani transaksi ke node yang dihosting dalam proses tersebut.

Transaksi sinkron dan asinkron

Binder mendukung transaksi sinkron dan asinkron. Bagian berikut menjelaskan cara setiap jenis transaksi dijalankan.

Transaksi sinkron

Transaksi sinkron diblokir hingga dieksekusi di node, dan balasan untuk transaksi tersebut telah diterima oleh pemanggil. Gambar berikut menunjukkan cara transaksi sinkron dijalankan:

Transaksi sinkron.

Gambar 1. Transaksi sinkron.

Untuk menjalankan transaksi sinkron, binder melakukan hal berikut:

  1. Thread di threadpool target (T2 dan T3) memanggil driver kernel untuk menunggu pekerjaan yang masuk.
  2. Kernel menerima transaksi baru dan mengaktifkan thread (T2) dalam proses target untuk menangani transaksi.
  3. Thread panggilan (T1) diblokir dan menunggu balasan.
  4. Proses target menjalankan transaksi dan menampilkan balasan.
  5. Thread dalam proses target (T2) memanggil kembali ke driver kernel untuk menunggu tugas baru.

Transaksi asinkron

Transaksi asinkron tidak memblokir penyelesaian; thread panggilan dibatalkan pemblokirannya segera setelah transaksi diteruskan ke kernel. Gambar berikut menunjukkan cara transaksi asinkron dieksekusi:

Transaksi asinkron.

Gambar 2. Transaksi asinkron.

  1. Thread di threadpool target (T2 dan T3) memanggil driver kernel untuk menunggu pekerjaan yang masuk.
  2. Kernel menerima transaksi baru dan mengaktifkan thread (T2) dalam proses target untuk menangani transaksi.
  3. Thread panggilan (T1) melanjutkan eksekusi.
  4. Proses target menjalankan transaksi dan menampilkan balasan.
  5. Thread dalam proses target (T2) memanggil kembali ke driver kernel untuk menunggu tugas baru.

Mengidentifikasi fungsi sinkron atau asinkron

Fungsi yang ditandai sebagai oneway dalam file AIDL bersifat asinkron. Contoh:

oneway void someCall();

Jika tidak ditandai sebagai oneway, fungsi adalah fungsi sinkron, meskipun fungsi menampilkan void.

Serialisasi transaksi asinkron

Binder menserialisasi transaksi asinkron dari satu node. Gambar berikut menunjukkan cara binder melakukan serialisasi transaksi asinkron:

Serialisasi transaksi asinkron.

Gambar 3. Serialisasi transaksi asinkron.

  1. Thread di threadpool target (B1 dan B2) memanggil driver kernel untuk menunggu tugas yang masuk.
  2. Dua transaksi (T1 dan T2) di node yang sama (N1) dikirim ke kernel.
  3. Kernel menerima transaksi baru dan, karena berasal dari node yang sama (N1), menserialisasinya.
  4. Transaksi lain di node yang berbeda (N2) dikirim ke kernel.
  5. Kernel menerima transaksi ketiga dan mengaktifkan thread (B2) dalam proses target untuk menangani transaksi.
  6. Proses target menjalankan setiap transaksi dan menampilkan balasan.

Transaksi bertingkat

Transaksi sinkron dapat bertingkat; thread yang menangani transaksi dapat mengeluarkan transaksi baru. Transaksi bertingkat dapat dilakukan ke proses yang berbeda, atau ke proses yang sama dengan yang Anda terima transaksi saat ini. Perilaku ini meniru panggilan fungsi lokal. Misalnya, misalkan Anda memiliki fungsi dengan fungsi bertingkat:

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

Jika ini adalah panggilan lokal, panggilan tersebut akan dieksekusi di thread yang sama. Khususnya, jika pemanggil inner_function juga merupakan proses yang menghosting node yang mengimplementasikan inner_inner_function, panggilan ke inner_inner_function akan dieksekusi di thread yang sama.

Gambar berikut menunjukkan cara binder menangani transaksi bertingkat:

Transaksi bertingkat.

Gambar 4. Transaksi bertingkat.

  1. Thread A1 meminta agar foo() dijalankan.
  2. Sebagai bagian dari permintaan ini, thread B1 menjalankan bar() yang dijalankan A pada thread A1 yang sama.

Gambar berikut menunjukkan eksekusi thread jika node yang menerapkan bar() berada dalam proses yang berbeda:

Transaksi bertingkat dalam proses yang berbeda.

Gambar 5. Transaksi bertingkat dalam proses yang berbeda.

  1. Thread A1 meminta agar foo() dijalankan.
  2. Sebagai bagian dari permintaan ini, thread B1 menjalankan bar() yang berjalan di thread C1 lainnya.

Gambar berikut menunjukkan cara thread menggunakan kembali proses yang sama di mana pun dalam rantai transaksi:

Transaksi bertingkat yang menggunakan kembali thread.

Gambar 6. Transaksi bertingkat yang menggunakan kembali thread.

  1. Proses A memanggil proses B.
  2. Proses B memanggil proses C.
  3. Kemudian, proses C melakukan panggilan kembali ke proses A dan kernel menggunakan kembali thread A1 dalam proses A yang merupakan bagian dari rantai transaksi.

Untuk transaksi asinkron, penyusunan tidak berperan; klien tidak menunggu hasil transaksi asinkron, sehingga tidak ada penyusunan. Jika pengendali transaksi asinkron membuat panggilan ke proses yang mengeluarkan transaksi asinkron tersebut, transaksi tersebut dapat ditangani di thread gratis mana pun dalam proses tersebut.

Menghindari kebuntuan

Gambar berikut menunjukkan kebuntuan umum:

Deadlock umum.

Gambar 7. Deadlock umum.

  1. Proses A mengambil mutex MA dan membuat panggilan pengikat (T1) ke proses B yang juga mencoba mengambil mutex MB.
  2. Secara bersamaan, proses B mengambil mutex MB dan melakukan panggilan pengikat (T2) ke proses A yang mencoba mengambil mutex MA.

Jika transaksi ini tumpang-tindih, setiap transaksi berpotensi mengambil mutex dalam prosesnya sambil menunggu proses lain melepaskan mutex, sehingga menyebabkan kebuntuan.

Untuk menghindari kebuntuan saat menggunakan binder, jangan menahan kunci apa pun saat melakukan panggilan binder.

Aturan pengurutan kunci dan kebuntuan

Dalam satu lingkungan eksekusi, kebuntuan sering dihindari dengan aturan pengurutan kunci. Namun, saat melakukan panggilan antar-proses dan antar-basis kode, terutama saat kode diperbarui, aturan pengurutan tidak dapat dipertahankan dan dikoordinasikan.

Mutex tunggal dan kebuntuan

Dengan transaksi bertingkat, proses B dapat memanggil kembali secara langsung ke thread yang sama dalam proses A yang memegang mutex. Oleh karena itu, karena rekursi yang tidak terduga, kebuntuan masih dapat terjadi dengan satu mutex.

Panggilan sinkron dan kebuntuan

Meskipun panggilan binder asinkron tidak memblokir penyelesaian, Anda juga harus menghindari penahanan kunci untuk panggilan asinkron. Jika Anda memegang kunci, Anda mungkin mengalami masalah penguncian jika panggilan satu arah secara tidak sengaja diubah menjadi panggilan sinkron.

Thread binder tunggal dan kebuntuan

Model transaksi Binder memungkinkan reentrancy, jadi meskipun proses memiliki satu thread binder, Anda tetap memerlukan penguncian. Misalnya, Anda melakukan iterasi pada daftar dalam proses A ber-thread tunggal. Untuk setiap item dalam daftar, Anda membuat transaksi binder keluar. Jika penerapan fungsi yang Anda panggil membuat transaksi binder baru ke node yang dihosting di proses A, transaksi tersebut akan ditangani di thread yang sama yang mengulangi daftar. Jika implementasi transaksi tersebut mengubah daftar yang sama, Anda dapat mengalami masalah saat Anda melanjutkan iterasi daftar tersebut nanti.

Mengonfigurasi ukuran threadpool

Jika layanan memiliki beberapa klien, menambahkan lebih banyak thread ke threadpool dapat mengurangi pertentangan dan melayani lebih banyak panggilan secara paralel. Setelah menangani konkurensi dengan benar, Anda dapat menambahkan lebih banyak thread. Masalah yang dapat disebabkan oleh penambahan lebih banyak thread yang mungkin tidak digunakan selama beban kerja yang tidak terlalu berat.

Thread dibuat sesuai permintaan hingga maksimum yang dikonfigurasi. Setelah thread binder dibuat, thread tersebut tetap aktif hingga proses yang menghostingnya berakhir.

Library libbinder memiliki default 15 thread. Gunakan setThreadPoolMaxThreadCount untuk mengubah nilai ini:

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