Thread latar belakang di Library Klien C++

Panduan ini menjelaskan model threading yang digunakan oleh library klien C++, dan menunjukkan cara mengganti kumpulan thread default dalam aplikasi Anda.

Tujuan

  • Menjelaskan model threading default untuk library klien C++.
  • Jelaskan cara mengganti default ini untuk aplikasi yang perlu melakukannya.

Mengapa library klien menggunakan thread latar belakang?

Sebagian besar fungsi di library klien menggunakan thread yang memanggil fungsi untuk menyelesaikan semua tugas, termasuk RPC apa pun ke layanan dan/atau memuat ulang token akses untuk autentikasi.

Fungsi asinkron, pada dasarnya, tidak dapat menggunakan thread saat ini untuk menyelesaikan tugasnya. Beberapa thread terpisah harus menunggu tugas selesai dan menangani respons.

Memblokir thread panggilan pada operasi yang berjalan lama juga akan sia-sia, yang mungkin memerlukan waktu beberapa menit atau lebih lama bagi layanan untuk menyelesaikan pekerjaan. Untuk operasi seperti itu, library klien menggunakan thread latar belakang untuk melakukan polling status operasi yang berjalan lama secara berkala.

Fungsi dan library apa yang memerlukan thread latar belakang?

Fungsi yang menampilkan future<T> untuk beberapa jenis T menggunakan thread latar belakang untuk menunggu hingga pekerjaan selesai.

Tidak semua library klien memiliki fungsi asinkron atau operasi yang berjalan lama. Library yang tidak memerlukannya tidak akan membuat thread latar belakang.

Anda mungkin melihat thread tambahan dalam aplikasi, tetapi thread tersebut mungkin dibuat oleh dependensi library klien C++, seperti gRPC. Thread ini biasanya kurang menarik karena tidak ada kode aplikasi yang pernah berjalan dalam thread ini dan hanya menyalurkan fungsi tambahan.

Bagaimana thread latar belakang ini memengaruhi aplikasi saya?

Seperti biasa, thread ini bersaing untuk mendapatkan resource CPU dan memori dengan aplikasi lainnya. Jika perlu, Anda dapat membuat kumpulan thread sendiri untuk mendapatkan kontrol yang baik atas setiap resource yang digunakan thread ini. Lihat detailnya di bawah.

Apakah ada kode saya yang berjalan di salah satu thread ini?

Ya. Saat Anda menambahkan callback ke future<T>, callback hampir selalu dieksekusi oleh salah satu thread latar belakang. Satu-satunya kasus saat hal ini tidak terjadi adalah jika future<T> sudah terpenuhi pada saat Anda memasang callback. Dalam hal ini, callback akan langsung dijalankan, dalam konteks thread yang menambahkan callback.

Misalnya, pertimbangkan aplikasi yang menggunakan library klien Pub/Sub. Panggilan Publish() akan menampilkan masa depan dan aplikasi dapat memasang callback setelah melakukan beberapa tugas:

namespace pubsub = ::google::cloud::pubsub;
namespace g = google::cloud;

void Callback(g::future<g::StatusOr<std::string>>);

void F(pubsub::Publisher publisher) {
  auto my_future = publisher.Publish(
      pubsub::MessageBuilder("Hello World!").Build());
  // do some work.
  my_future.then(Callback);
}

Jika my_future terpenuhi sebelum fungsi .then() dipanggil, callback akan segera dipanggil. Jika ingin memastikan kode berjalan di thread terpisah, Anda harus menggunakan kumpulan thread sendiri dan menyediakan fungsi callable di .then() yang meneruskan eksekusi ke kumpulan thread Anda.

Kumpulan Thread Default

Untuk library yang memerlukan thread latar belakang, Make*Connection() membuat kumpulan thread default. Kecuali jika Anda mengganti kumpulan thread, setiap objek *Connection memiliki kumpulan thread terpisah.

Kumpulan thread default di sebagian besar library berisi satu thread. Thread lainnya jarang diperlukan karena thread latar belakang digunakan untuk memeriksa status operasi yang berjalan lama. Panggilan ini cukup berumur pendek dan menggunakan CPU yang sangat sedikit, sehingga satu thread latar belakang dapat menangani ratusan operasi yang berjalan lama yang tertunda, dan sangat sedikit aplikasi yang memiliki jumlah CPU sebanyak itu.

Operasi asinkron lainnya mungkin memerlukan lebih banyak resource. Gunakan GrpcBackgroundThreadPoolSizeOption untuk mengubah ukuran kumpulan thread latar belakang default jika diperlukan.

Library Pub/Sub diperkirakan akan melakukan pekerjaan yang jauh lebih banyak, karena biasanya aplikasi Pub/Sub menerima atau mengirim ribuan pesan per detik. Oleh karena itu, library ini ditetapkan secara default ke satu thread per inti pada arsitektur 64-bit. Pada arsitektur 32-bit (atau saat dikompilasi dalam mode 32-bit, meskipun berjalan pada arsitektur 64-bit), default ini hanya akan berubah menjadi 4 thread.

Menyediakan Kumpulan Thread Anda sendiri

Anda dapat menyediakan kumpulan thread Anda sendiri untuk thread latar belakang. Buat objek CompletionQueue, lampirkan thread ke objek tersebut, dan konfigurasikan GrpcCompletionQueueOption saat melakukan inisialisasi klien. Contoh:

namespace admin = ::google::cloud::spanner_admin;
namespace g = ::google::cloud;

void F() {
  // You will need to create threads
  auto cq = g::CompletionQueue();
  std::vector<std::jthread> threads;
  for (int i = 0; i != 10; ++i) {
    threads.emplace_back([](auto cq) { cq.Run(); }, cq);
  }
  auto client = admin::InstanceAdminClient(admin::MakeInstanceAdminConnection(
      g::Options{}.set<g::GrpcCompletionQueueOption>(cq)));
  // Use `client` as usual
}

Anda dapat berbagi objek CompletionQueue yang sama di beberapa klien, bahkan untuk layanan yang berbeda:

namespace admin = ::google::cloud::spanner_admin;
namespace admin = ::google::cloud::pubsub;
namespace g = ::google::cloud;

void F(pubsub::Topic const& topic1, pubsub::Topic const& topic2) {
  // You will need to create threads
  auto cq = g::CompletionQueue();
  std::vector<std::jthread> threads;
  for (int i = 0; i != 10; ++i) {
    threads.emplace_back([](auto cq) { cq.Run(); }, cq);
  }
  auto client = admin::InstanceAdminClient(admin::MakeInstanceAdminConnection(
      g::Options{}.set<g::GrpcCompletionQueue>(cq)));
  auto p1 = pubsub::Publisher(pubsub::MakePublisherConnection(
      topic1, g::Options{}.set<g::GrpcCompletionQueueOption>(cq)));
  auto p2 = pubsub::Publisher(pubsub::MakePublisherConnection(
      topic2, g::Options{}.set<g::GrpcCompletionQueueOption>(cq)));
  // Use `client`, `p1`, and `p2` as usual
}

Langkah Berikutnya