En esta guía, se describe el modelo de subprocesos que usan las bibliotecas cliente de C++ y se muestra cómo anular los grupos de subprocesos predeterminados en tu aplicación.
Objetivos
- Describe el modelo de subprocesos predeterminado para las bibliotecas cliente de C++.
- Describe cómo anular estos valores predeterminados para las aplicaciones que lo necesiten.
¿Por qué las bibliotecas cliente usan subprocesos en segundo plano?
La mayoría de las funciones de las bibliotecas cliente usan el subproceso que llama a la función para completar todo el trabajo, incluidas las RPCs al servicio o la actualización de los tokens de acceso para la autenticación.
Por su naturaleza, las funciones asíncronas no pueden usar el subproceso actual para completar su trabajo. Algún subproceso independiente debe esperar a que se complete el trabajo y controlar la respuesta.
También es un desperdicio bloquear el subproceso de llamada en operaciones de larga duración, en las que el servicio puede tardar minutos o más en completar el trabajo. Para estas operaciones, la biblioteca cliente usa subprocesos en segundo plano para sondear periódicamente el estado de la operación de larga duración.
¿Qué funciones y bibliotecas requieren subprocesos en segundo plano?
Las funciones que devuelven un future<T> para algún tipo T usan subprocesos en segundo plano para esperar hasta que se complete el trabajo.
No todas las bibliotecas cliente tienen funciones asíncronas ni operaciones de larga duración. Las bibliotecas que no los necesitan no crean subprocesos en segundo plano.
Es posible que observes subprocesos adicionales en tu aplicación, pero estos pueden ser creados por dependencias de la biblioteca cliente de C++, como gRPC. Por lo general, estos subprocesos son menos interesantes, ya que nunca se ejecuta código de la aplicación en ellos y solo cumplen funciones auxiliares.
¿Cómo afectan estos subprocesos en segundo plano a mi aplicación?
Como de costumbre, estos subprocesos compiten por los recursos de CPU y memoria con el resto de tu aplicación. Si es necesario, puedes crear tu propio grupo de subprocesos para obtener un control preciso de los recursos que usan estos subprocesos. Consulta la siguiente información para obtener más detalles.
¿Se ejecuta parte de mi código en alguno de estos subprocesos?
Sí. Cuando adjuntas una devolución de llamada a un objeto future<T>, la devolución de llamada casi siempre se ejecuta en uno de los subprocesos en segundo plano. El único caso en el que esto no sucedería es si future<T> ya se cumple cuando adjuntas la devolución de llamada. En ese caso, la devolución de llamada se ejecuta de inmediato, en el contexto del subproceso que adjunta la devolución de llamada.
Por ejemplo, considera una aplicación que usa la biblioteca cliente de Pub/Sub.
La llamada a Publish() devuelve un futuro, y la aplicación puede adjuntar una devolución de llamada después de realizar algún trabajo:
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);
}
Si se cumple my_future antes de que se llame a la función .then(), se invoca la devolución de llamada de inmediato. Si deseas garantizar que el código se ejecute en un subproceso independiente, debes usar tu propio grupo de subprocesos y proporcionar un elemento invocable en .then() que reenvíe la ejecución a tu grupo de subprocesos.
Conjuntos de subprocesos predeterminados
Para las bibliotecas que requieren subprocesos en segundo plano, Make*Connection() crea un grupo de subprocesos predeterminado. A menos que anules el conjunto de subprocesos, cada objeto *Connection tendrá un conjunto de subprocesos independiente.
El grupo de subprocesos predeterminado en la mayoría de las bibliotecas contiene un solo subproceso. Rara vez se necesitan más subprocesos, ya que el subproceso en segundo plano se usa para sondear el estado de las operaciones de larga duración. Estas llamadas son razonablemente breves y consumen muy poca CPU, por lo que un solo subproceso en segundo plano puede controlar cientos de operaciones pendientes de larga duración, y muy pocas aplicaciones tienen incluso esa cantidad.
Otras operaciones asíncronas pueden requerir más recursos.
Usa GrpcBackgroundThreadPoolSizeOption para cambiar el tamaño predeterminado del grupo de subprocesos en segundo plano si es necesario.
Se espera que la biblioteca de Pub/Sub tenga mucho más trabajo, ya que es común que las aplicaciones de Pub/Sub reciban o envíen miles de mensajes por segundo. Por lo tanto, esta biblioteca usa de forma predeterminada un subproceso por núcleo en arquitecturas de 64 bits. En las arquitecturas de 32 bits (o cuando se compila en modo de 32 bits, incluso si se ejecuta en una arquitectura de 64 bits), este valor predeterminado cambia a solo 4 subprocesos.
Cómo proporcionar tu propio conjunto de subprocesos
Puedes proporcionar tu propio grupo de subprocesos para los subprocesos en segundo plano. Crea un objeto CompletionQueue, adjúntale subprocesos y configura GrpcCompletionQueueOption cuando inicialices tu cliente. Por ejemplo:
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
}
Puedes compartir el mismo objeto CompletionQueue entre varios clientes, incluso para diferentes servicios:
namespace admin = ::google::cloud::spanner_admin;
namespace pubsub = ::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
}
Próximos pasos
- Consulta Configuración de la biblioteca cliente para obtener más información sobre las opciones de configuración comunes de la biblioteca.
- Consulta la biblioteca cliente de C++ de Cloud Pub/Sub para obtener la documentación de referencia completa de la biblioteca cliente de Pub/Sub.