c++ boost boost-asio
Ejemplo de Directory Monitorejemplo de monitor de directorio

c++ - Intentando comprender la implementación del servicio personalizado de Boost.Asio



boost-asio (1)

Estoy pensando en escribir un servicio personalizado de Asio además de un protocolo de red de terceros propio que actualmente estamos usando.

Según la guía Highscore Asio , debe implementar tres clases para crear un servicio personalizado de Asio:

  • Una clase derivada de boost::asio::basic_io_object representa el nuevo objeto de E / S.
  • Una clase derivada de boost::asio::io_service::service representa un servicio que está registrado con el servicio de E / S y se puede acceder desde el objeto de E / S.
  • Una clase no derivada de ninguna otra clase que represente la implementación del servicio.

La implementación del protocolo de red ya proporciona operaciones asincrónicas y tiene un bucle de evento (de bloqueo). Así que pensé, lo pondría en mi clase de implementación de servicios y ejecutaría el bucle de eventos en un hilo de trabajo interno. Hasta aquí todo bien.

Al observar algunos ejemplos de servicios personalizados, noté que las clases de servicio engendran sus propios hilos internos (de hecho crean instancias de sus propias instancias internas de io_service). Por ejemplo:

  1. La página Highscore proporciona un ejemplo de monitor de directorio . Es esencialmente una envoltura alrededor de inotify. Las clases interesantes son inotify/basic_dir_monitor_service.hpp e inotify/dir_monitor_impl.hpp . Dir_monitor_impl maneja la interacción real con inofity, que es de bloqueo, y por lo tanto se ejecuta en una cadena de fondo. Estoy de acuerdo con eso. Pero el basic_dir_monitor_service también tiene un subproceso interno de trabajo y todo lo que parece estar haciendo es mezclar solicitudes entre main''s io_service y dir_monitor_impl . basic_dir_monitor_service con el código, basic_dir_monitor_service hilo de trabajo en basic_dir_monitor_service y, en su lugar, basic_dir_monitor_service solicitudes directamente al servicio io_ principal y el programa aún se ejecutó como antes.

  2. En el ejemplo del servicio de registro personalizado de Asio, he notado el mismo enfoque. El servicio logger_service genera un hilo de trabajo interno para manejar las solicitudes de registro. No he tenido tiempo de jugar con ese código, pero creo que también debería ser posible publicar estas solicitudes directamente en el servicio principal de io.

¿Cuál es la ventaja de tener estos "trabajadores intermediarios"? ¿No podría publicar todo el trabajo en el io_service principal todo el tiempo? ¿Extrañé algún aspecto crucial del patrón de Proactor?

Probablemente debería mencionar que estoy escribiendo software para un sistema incrustado de un solo núcleo. Tener estos hilos adicionales en su lugar parece imponer cambios de contexto innecesarios que me gustaría evitar si es posible.


En resumen, consistencia. Los servicios intentan cumplir con las expectativas del usuario establecidas por los servicios de Boost.Asio.

El uso de un io_service interno proporciona una clara separación de la propiedad y el control de los controladores. Si un servicio personalizado publica sus controladores internos en el io_service del usuario, la ejecución de los controladores internos del servicio queda acoplada implícitamente con los controladores del usuario. Considere cómo esto afectaría las expectativas del usuario con el ejemplo del Boost.Asio Logger Service :

  • El servicio logger_service escribe en la secuencia de archivos dentro de un controlador. Por lo tanto, un programa que nunca procesa el bucle de eventos io_service , como uno que solo usa la API síncrona, nunca tendrá mensajes de registro escritos.
  • El servicio logger_service ya no sería seguro para subprocesos, lo que podría io_service un comportamiento indefinido si varios subprocesos procesan el servicio io_service .
  • La duración de las operaciones internas de logger_service está restringida por la del servicio io_service . Por ejemplo, cuando se invoca la función shutdown_service() un servicio, la duración del servicio io_service ya ha finalizado. Por lo tanto, los mensajes no se pudieron registrar a través de logger_service::log() dentro de shutdown_service() , ya que intentaría publicar un controlador interno en el io_service cuya vida útil ya ha finalizado.
  • El usuario ya no puede asumir un mapeo uno a uno entre una operación y el manejador. Por ejemplo:

    boost::asio::io_service io_service; debug_stream_socket socket(io_service); boost::asio::async_connect(socket, ..., &connect_handler); io_service.poll(); // Can no longer assume connect_handler has been invoked.

    En este caso, io_service.poll() puede invocar el controlador interno al logger_service , en lugar de connect_handler() .

Además, estos subprocesos internos intentan imitar el comportamiento utilizado internamente por Boost.Asio itself :

La implementación de esta biblioteca para una plataforma particular puede hacer uso de uno o más subprocesos internos para emular la asincronicidad. En la medida de lo posible, estos hilos deben ser invisibles para el usuario de la biblioteca.

Ejemplo de Directory Monitor

En el ejemplo de monitor de directorio, se utiliza un subproceso interno para evitar el bloqueo indefinido del io_service del usuario mientras se espera un evento. Una vez que se ha producido un evento, el controlador de finalización está listo para ser invocado, por lo que el subproceso interno publica el controlador de usuario en el io_service del usuario para la invocación diferida. Esta implementación emula asynchronicity con un hilo interno que es en su mayoría invisible para el usuario.

Para obtener más información, cuando se inicia una operación de monitor asíncrona a través de dir_monitor::async_monitor() , se basic_dir_monitor_service::monitor_operation un basic_dir_monitor_service::monitor_operation en el io_service interno. Cuando se invoca, esta operación invoca dir_monitor_impl::popfront_event() , una llamada potencialmente bloqueante. Por lo tanto, si el monitor_operation se publica en el io_service del usuario, el hilo del usuario podría bloquearse indefinidamente. Considere el efecto en el siguiente código:

boost::asio::io_service io_service; boost::asio::dir_monitor dir_monitor(io_service); dir_monitor.add_directory(dir_name); // Post monitor_operation into io_service. dir_monitor.async_monitor(...); io_service.post(&user_handler); io_service.run();

En el código anterior, si io_service.run() invoca monitor_operation primero, entonces user_handler() no se invocará hasta que dir_monitor observe un evento en el directorio dir_name . Por lo tanto, la implementación del servicio dir_monitor no se comportaría de manera consistente que la mayoría de los usuarios esperan de otros servicios.

Servicio Asio Logger

El uso de un hilo interno y io_service :

  • Mitiga la sobrecarga de iniciar sesión en el (los) hilo (s) del usuario realizando llamadas potencialmente costosas o de bloqueo dentro del hilo interno.
  • Garantiza la seguridad de subprocesos de std::ofstream , ya que solo el único subproceso interno escribe en la secuencia. Si el registro se realizó directamente dentro de logger_service::log() o si logger_service publicó sus manejadores en el io_service del usuario, entonces se requeriría una sincronización explícita para la seguridad de los hilos. Otros mecanismos de sincronización pueden introducir una mayor sobrecarga o complejidad en la implementación.
  • Permite que los services registren mensajes dentro de shutdown_service() . Durante la destruction , el io_service :

    1. Cierre cada uno de sus servicios.
    2. Destruye todos los controladores no invocados que estaban programados para la invocación diferida en el io_service o cualquiera de sus strand asociadas.
    3. Destruye cada uno de sus servicios.


    Como la vida útil del servicio io_service del usuario ha finalizado, su cola de eventos no se está procesando ni se pueden publicar controladores adicionales. Al tener su propio io_service interno procesado por su propio hilo, logger_service permite a otros servicios registrar mensajes durante shutdown_service() .

consideraciones adicionales

Al implementar un servicio personalizado, aquí hay algunos puntos a considerar:

  • Bloquea todas las señales en los hilos internos.
  • Nunca invoque el código del usuario directamente.
  • Cómo rastrear y publicar controladores de usuario cuando se destruye una implementación.
  • Recurso (s) propiedad del servicio que se comparten entre las implementaciones del servicio.

Para los dos últimos puntos, el dir_monitor E / S dir_monitor exhibe un comportamiento que los usuarios pueden no esperar. Como el único hilo dentro del servicio invoca una operación de bloqueo en la cola de eventos de una sola implementación, bloquea efectivamente las operaciones que podrían completarse de manera inmediata para su respectiva implementación:

boost::asio::io_service io_service; boost::asio::dir_monitor dir_monitor1(io_service); dir_monitor1.add_directory(dir_name1); dir_monitor1.async_monitor(&handler_A); boost::asio::dir_monitor dir_monitor2(io_service); dir_monitor2.add_directory(dir_name2); dir_monitor2.async_monitor(&handler_B); // ... Add file to dir_name2. { // Use scope to enforce lifetime. boost::asio::dir_monitor dir_monitor3(io_service); dir_monitor3.add_directory(dir_name3); dir_monitor3.async_monitor(&handler_C); } io_service.run();

Aunque las operaciones asociadas con handler_B() (success) y handler_C() (abortado) no se bloquearían, el hilo único en basic_dir_monitor_service está bloqueado esperando un cambio a dir_name1 .