SObjectizer es un marco de trabajo de C ++ 17 relativamente pequeño que le permite utilizar enfoques como Modelo de actor, Publicar-Suscribirse y Comunicar procesos secuenciales (CSP) en programas de C ++. Lo que simplifica enormemente el desarrollo de aplicaciones complejas multiproceso en C ++. Si el lector oye acerca de SObjectizer por primera vez, puede impresionarlo en esta presentación o en este artículo ya bastante antiguo.
En términos generales, no hay tantas herramientas abiertas, todavía vivas y en desarrollo similares para C ++. Uno solo puede recordar QP / C ++ , CAF: C ++ Actor Framework , actor-zeta y el proyecto de rotor muy joven. Hay una opción, pero no tan grande.
Recientemente, otra versión "principal" de SObjectizer ha estado disponible, donde finalmente ha aparecido algo de lo que se ha hablado durante mucho tiempo, y al que me he acercado varias veces sin éxito. Podemos decir que se ha alcanzado un hito. Esta es también una ocasión para hablar sobre lo que SObjectizer esperará después del lanzamiento de la versión 5.7.0.
Soporte Send_case en select ()
Por lo tanto, la innovación más importante que apareció en v.5.7.0 y para la cual la compatibilidad con v.5.6 lanzada el año pasado (y no rompemos la compatibilidad) es el soporte para send_case en la función select (). Lo que hizo que SObjectizer's select () se parezca mucho más a Go select. Ahora, usando select (), no solo puede leer mensajes de varios canales CSP, sino también enviar mensajes salientes a aquellos canales que estaban listos para ser escritos.
Pero para revelar este tema, debe comenzar desde lejos.
La aparición de elementos CSP en SObjectizer-5
Los elementos de CSP, a saber, los análogos de los canales de CSP, aparecieron en SObjectizer-5 no para marcar la casilla "Soporte de CSP", sino para resolver un problema práctico.
La cuestión era que cuando toda la aplicación se basa completamente en SObjectizer, el intercambio de información entre varias entidades (partes) del programa se realiza de la única manera obvia. Todo en la aplicación se presenta en forma de agentes (actores) y los agentes simplemente se envían mensajes entre sí de manera estándar.
Pero cuando en la aplicación solo se implementa parte de la funcionalidad en SObjectizer ...
Por ejemplo, una aplicación GUI en Qt o wxWidgets, en la que la parte principal del código es una GUI, y se necesita un SObjectizer para realizar algunas tareas en segundo plano. O parte de la aplicación se escribe utilizando hilos desnudos y Asio, y los datos leídos por Asio desde la red se envían a los agentes de SObjectizer para su procesamiento.
Cuando una aplicación tiene una parte SObjectizer y una parte no SObjectizer, surge la pregunta: ¿cómo transferir información desde la parte SObjectizer de la aplicación a la parte no SObjectizer?
La solución se encontró en forma de la llamada cadenas de mensajes (mchains), es decir conversaciones Lo cual, simplemente sucedió, resultó ser la esencia de los canales CSP. La parte SObjectizer de la aplicación envía mensajes a mchain de la manera habitual, utilizando la función regular send ().
Para leer mensajes de la parte que no es SObjectizer, puede usar la nueva función de recepción (), para usar la cual no necesitaba crear agentes o sumergirse en cualquier otro comodín de SObjectizer.
Resultó bastante entendible y esquema de trabajo.
Mal uso de mchains
Además, el esquema resultó ser tan comprensible y funcional que rápidamente algunas aplicaciones en SObjectizer comenzaron a escribir sin ningún agente, solo en mchain-ahs. Es decir utilizando el enfoque CSP, no el modelo de actor. Ya había artículos al respecto aquí en Habré: uno y dos .
Esto condujo a dos consecuencias interesantes.
Primero, la función de recepción () ha crecido demasiado con funciones avanzadas. Esto era necesario para que fuera posible hacer una sola llamada para recibir (), cuyo retorno se produciría cuando ya se haya realizado todo el trabajo necesario. Estos son ejemplos de lo que puede hacer SObjectizer's reciben ():
using namespace so_5;
En segundo lugar, pronto quedó claro que, a pesar de que se pueden colocar varios tipos de mensajes en el mchain de SObjectizer, e incluso a pesar de la presencia de una función de recepción avanzada (), a veces es necesario poder trabajar con varios canales a la vez ...
seleccione () pero solo lectura
La función select () se ha agregado a SObjectizer para leer y procesar mensajes de varias cadenas. Clear business select () apareció no solo así, sino bajo la influencia del lenguaje Go. Pero select () de SObjectizer tenía dos características.
En primer lugar, nuestro select (), como recibir (), estaba orientado a scripts, cuando select () se llama solo una vez y todo el trabajo útil se realiza dentro de él. Por ejemplo:
using namespace so_5; mchain_t ch1 = env.create_mchain(...); mchain_t ch2 = env.create_mchain(...);
En segundo lugar, select () no admitía el envío de mensajes al canal. Es decir fue posible leer mensajes de canales. Pero para enviar mensajes al canal usando select () - no.
Ahora incluso es difícil recordar por qué sucedió. Probablemente porque select () con el soporte de send_case resultó ser una tarea difícil y no se encontraron recursos para resolverlo.
mchain's en SObjectizer es más complicado que los canales en Go
Inicialmente, select () sin el soporte de send_case no se consideraba un problema. El hecho es que las cadenas en SObjectizer tienen sus propios detalles que los canales Go no tienen.
En primer lugar, las máquinas SObjectizer se dividen en adimensionales y con una capacidad máxima fija. Por lo tanto, si send () se ejecuta para una mchain adimensional, este send () no se bloqueará en principio. Por lo tanto, no tiene sentido usar select () para enviar un mensaje a la mchain adimensional.
En segundo lugar, para mchains con una capacidad máxima fija, cuando se crea, indica inmediatamente lo que sucede cuando intenta escribir un mensaje en mchain completo:
- ¿Debo esperar la aparición de espacio libre en mchain? Y si es necesario, cuánto tiempo;
- si no hay espacio libre, entonces qué hacer: eliminar el mensaje más antiguo de mchain, ignorar el nuevo mensaje, lanzar una excepción o incluso llamar a std :: abort () (este script difícil es muy solicitado en la práctica).
Por lo tanto, un escenario bastante frecuente (hasta donde yo sé) de usar select en Go para enviar un mensaje que no bloquea estrictamente goroutin estaba inmediatamente disponible en SObjectizer sin chispas y sin select.
Al final, una selección completa ()
Sin embargo, el tiempo pasó, ocasionalmente hubo casos en que la falta de soporte de send_case en select () aún se ve afectada. Además, en estos casos, las capacidades integradas de mchains no ayudaron, sino todo lo contrario.
Por lo tanto, de vez en cuando intenté abordar el problema de la implementación de send_case. Pero hasta hace poco, nada funcionaba. Principalmente porque no fue posible idear el diseño de este send_case en sí. Es decir ¿Cómo debería verse send_case dentro de select ()? ¿Qué debe hacer exactamente si es posible enviar? En caso de imposibilidad? ¿Qué hacer con la división en cadenas adimensionales y fijas?
Fue posible encontrar respuestas que me sentaron a estas y otras preguntas solo en diciembre de 2019. En gran parte debido a las consultas con personas que están familiarizadas con Go y han utilizado las selecciones Go en el trabajo real. Bueno, tan pronto como la imagen de send_case finalmente tomó forma, la implementación llegó allí.
Entonces ahora puedes escribir así:
using namespace so_5; struct Greeting { std::string text_; }; select(from_all().handle_n(1), send_case(ch, message_holder_t<Greeting>::make("Hello!"), []{ std::cout << "Hello sent!" << std::endl; }));
Lo importante es que send_case en select () ignora la respuesta de sobrecarga que se configuró para el mchain objetivo. Entonces, en el ejemplo anterior, ch podría crearse con la reacción abort_app al intentar enviar un mensaje al canal completo. Y si intenta llamar a simple send () para escribir en ch, se puede llamar a std :: abort (). Pero en el caso de select (), y esto no sucederá, select () esperará hasta que aparezca espacio libre en el cap. O hasta que ch esté cerrado.
Aquí hay algunos ejemplos más de lo que send_case puede hacer en select () de SObjectizer:
using namespace so_5;
Naturalmente, send_case en select () se puede usar junto con accept_case:
Ahora, en SObjectizer, el enfoque CSP puede usarse, como dicen, en todos los campos. No será peor que en Go. Detallado, por supuesto. Pero no peor :)
Podemos decir que la larga historia de agregar soporte para el enfoque CSP a SObjectizer ha terminado.
Otras cosas importantes en esta versión
Movimiento final a github
SObjectizer originalmente vivió y se desarrolló en SourceForge . Un año de comerciales desde 2006. Pero en SF.net, el rendimiento de Subversion fue disminuyendo cada vez más, por lo que el año pasado nos mudamos a BitBucket y Mercurial. Tan pronto como hicimos esto, Atlassian anunció que los repositorios de Mercurial con BitBucket pronto se eliminarían por completo. Por lo tanto, desde agosto de 2019, tanto SObjectizer como so5extra se encuentran en GitHub.
SF.net tiene todo el contenido anterior, incluido el Wiki con documentación para versiones anteriores de SObjectizer. Y también la sección Archivos desde donde puede descargar archivos de diferentes versiones de SObjectizer / so5extra y no solo (por ejemplo, archivos PDF con algunas presentaciones sobre SObjectizer ).
En general, búscanos ahora en GitHub . Y no te olvides de poner estrellas, tenemos muy pocas por ahora;)
Comportamiento fijo de mensajes envueltos
En SO-5.7.0, se realizó una pequeña corrección que no podría haber sido mencionada. Pero vale la pena decirlo, porque esta es una buena demostración de cómo las diversas características que se acumulan en SObjectizer se afectan entre sí durante su desarrollo.
Hace cuatro años, se agregó soporte para agentes, que son máquinas de estado jerárquicas, a SObjectizer (más detalles aquí ). Luego, después de otro par de años, se agregaron sobres de mensajes a SObjectizer. Es decir el mensaje, cuando se envió, estaba envuelto en un objeto de sobre adicional y este sobre podría recibir información sobre lo que está sucediendo con el mensaje.
Una de las características del mecanismo de mensajes envueltos es que se informa al sobre que el mensaje ha sido entregado al destinatario. Es decir, que se encontró un controlador para este mensaje en el agente del suscriptor y que se llamó a este controlador.
Resultó que si el agente receptor del mensaje es una máquina de estados jerárquica que utiliza una función como suppress()
(es decir, forzar el mensaje a ignorarse en un estado específico), el sobre puede recibir una notificación de entrega incorrecta, aunque el destinatario realmente rechazó el mensaje debido a suppress()
. Una situación aún más interesante fue con transfer_to_state()
, porque después de cambiar el estado del agente receptor, se puede encontrar el controlador de mensajes o puede estar ausente. Pero el sobre sobre la entrega del mensaje fue informado de todos modos.
Casos muy raros, que, hasta donde yo sé, no han sido mostrados en la práctica por nadie. Sin embargo, se hizo un error de cálculo.
Por lo tanto, en SO-5.7.0 este punto se mejora y si el mensaje se ignora como resultado de aplicar suppress()
o transfer_to_state()
, el sobre ya no pensará que el mensaje ha sido entregado al destinatario.
En 2017, comenzamos a hacer una biblioteca de componentes adicionales para SObjectizer llamada so5extra . Durante este tiempo, la biblioteca ha crecido significativamente y contiene muchas cosas útiles en el hogar.
So5extra se distribuyó originalmente bajo una doble licencia: GNU Affero GPL v.3 para proyectos de código abierto y comerciales para proyectos cerrados.
Ahora hemos cambiado la licencia de so5extra y, a partir de la versión 1.4.0, so5extra se distribuye bajo la licencia BSD-3-CLAUSE. Es decir se puede usar de forma gratuita incluso cuando se desarrolla software propietario.
Por lo tanto, si le falta algo en SObjectizer, puede echar un vistazo a so5extra , ¿qué sucede si ya tiene lo que necesita?
El futuro de SObjectizer
Antes de decir algunas palabras sobre lo que SObjectizer está esperando, debe hacer una digresión importante. Especialmente para aquellos que creen que SObjectizer es un "desperdicio de referencia", "mano de obra hasta la rodilla", "laboratorio de estudiantes", "proyección experimental que los autores abandonan cuando juegan lo suficiente" ... (esto es solo una parte de las características que habíamos escuchado de expertos en de nuestro internet en los últimos 4-5 años).
He estado desarrollando SObjectizer durante casi dieciocho años. Y puedo decir responsablemente que nunca fue un proyecto piloto. Esta es una herramienta práctica que entró en trabajo real desde su primera versión en el año 2002.
Tanto yo como mis colegas, y las personas que nos atrevimos a tomar y probar SObjectizer, nos convencimos muchas veces de que SObjectizer realmente hace que el desarrollo de algunos tipos de aplicaciones C ++ multiproceso sea mucho más fácil. Por supuesto, SObjectizer no es una bala de plata, y de ninguna manera siempre se puede usar. Pero cuando corresponde, ayuda.
La vida regularmente brinda una oportunidad una vez más para estar convencido de esto. De vez en cuando, el código multiproceso de otra persona nos llama la atención, en el que no había nada similar a SObjectizer y es poco probable que aparezca. Trate este código aquí y allá, los momentos son sorprendentes cuando el uso de actores o canales CSP podría hacer que el código sea más simple y confiable. Pero no, debe crear patrones no triviales de interacción de subprocesos mediante mutex-s y condition_variables, donde en SObjectizer puede administrar con una mchain, un par de mensajes y un temporizador integrado en SObjectizer. Y luego también pasa mucho tiempo para probar estos esquemas no triviales ...
Entonces SObjectizer fue útil para nosotros. Me atrevo a pensar que fue útil no solo para nosotros. Y lo más importante, hace tiempo que está aquí y está disponible gratuitamente para todos. No se irá a ningún lado. ¿Y dónde ir a lo que hay en OpenSource bajo una licencia permisiva? ;)
Otra cosa es que nosotros mismos implementamos toda nuestra gran lista de deseos en SObjectizer. Y el desarrollo futuro de SObjectizer estará determinado no tanto por nuestras necesidades como por los deseos de los usuarios.
Habrá tales deseos, habrá nuevas funciones en SObjectizer.
No será ... Bueno, entonces solo emitiremos versiones correctivas de vez en cuando y verificaremos el rendimiento de SObjectizer en las nuevas versiones de compiladores de C ++.
Entonces, si desea ver algo en SObjectizer, háganoslo saber. Si necesita ayuda con SObjectizer, no dude en contactarnos (a través de Problemas en GitHub o en el grupo de Google ), definitivamente intentaremos ayudarlo.
Bueno, quiero agradecer a los lectores que pudieron leer hasta el final de este artículo. Y trataré de responder cualquier pregunta sobre SObjectizer / so5extra, en caso de que surja.
PS. Estaría agradecido si los lectores encontraran tiempo para escribir en los comentarios si fue interesante / útil leer artículos sobre SObjectizer y si quieren hacer esto en el futuro. ¿O es mejor para nosotros dejar de perder el tiempo escribiendo tales artículos, y así dejar de tomar el tiempo de los usuarios de Habr?
PPS ¿O tal vez alguien consideró a SObjectizer como una herramienta que no podría aplicarse por una razón u otra? Sería muy interesante saber sobre esto.