Este artículo puede resultar demasiado técnico para la mayoría de los lectores . ( Enero de 2013 ) |
Paradigma | computación concurrente , programación distribuida |
---|---|
Revelador | INRIA Inria |
Sitio web | Inria Únete |
Implementaciones principales | |
Unir Java, C # polifónico , C paralelo unificado , Cω , biblioteca de uniones , Boost . | |
Influenciado | |
Únete a Cálculo |
Join-patterns proporciona una forma de escribir programas informáticos concurrentes , paralelos y distribuidos mediante el paso de mensajes . En comparación con el uso de subprocesos y bloqueos, este es un modelo de programación de alto nivel que utiliza un modelo de construcción de comunicación para abstraer la complejidad del entorno concurrente y permitir la escalabilidad . Su enfoque está en la ejecución de un acorde entre mensajes consumidos atómicamente desde un grupo de canales.
Esta plantilla se basa en el cálculo de uniones y utiliza la coincidencia de patrones . En concreto, esto se hace permitiendo la definición de uniones de varias funciones y/o canales mediante la coincidencia de patrones de llamadas y mensajes simultáneos. Es un tipo de patrón de concurrencia porque hace más fácil y flexible que estas entidades se comuniquen y gestionen con el paradigma de programación multiproceso.
El patrón de unión (o un acorde en Cω ) es como una supertubería con sincronización y emparejamiento. De hecho, este concepto se resume en emparejar y unir un conjunto de mensajes disponibles de diferentes colas de mensajes , y luego manejarlos todos simultáneamente con un controlador. [1] Podría representarse con las palabras clave when
para especificar la primera comunicación que esperábamos, con el and
para unir/emparejar otros canales y el do
para ejecutar algunas tareas con los diferentes mensajes recopilados. Un patrón de unión construido normalmente toma esta forma:
j.Cuando(a1).Y(a2)....Y(an).Haz(d)
El argumento a1 de
When(a1)
puede ser un canal sincrónico o asincrónico o una matriz de canales asincrónicos. Cada argumento ai posterior a (for ) debe ser un canal asincrónico. [2]And(ai)
i > 1
Más precisamente, cuando un mensaje coincide con una cadena de patrones vinculados, hace que su controlador se ejecute (en un nuevo hilo si está en un contexto asincrónico); de lo contrario, el mensaje se pone en cola hasta que se habilite uno de sus patrones; si hay varias coincidencias, se selecciona un patrón no especificado. [3]
A diferencia de un controlador de eventos, que atiende uno de varios eventos alternativos a la vez, junto con todos los demás controladores de ese evento, un patrón de unión espera una conjunción de canales y compite por la ejecución con cualquier otro patrón habilitado. [4]
El patrón de unión se define mediante un conjunto de canales de cálculo pi x que admiten dos operaciones diferentes, enviar y recibir. Necesitamos dos nombres de cálculo de unión para implementarlo: un nombre de canal x para enviar (un mensaje) y un nombre de función x para recibir un valor (una solicitud). El significado de la definición de unión es que una llamada a devuelve un valor que se envió en un canal . Cada vez que las funciones se ejecutan simultáneamente, se activa el proceso de retorno y se sincroniza con otras uniones. [5]x()
x<>
J ::= //patrones de unión | x < y > //patrón de envío de mensajes | x ( y ) //patrón de llamada de función | J | JBIS //sincronización
Desde la perspectiva de un cliente, un canal simplemente declara un método con el mismo nombre y firma. El cliente publica un mensaje o emite una solicitud invocando el canal como método. Un método de continuación debe esperar hasta que llegue una única solicitud o mensaje a cada uno de los canales que siguen a la cláusula When de la continuación. Si la continuación se ejecuta, los argumentos de cada invocación de canal se sacan de la cola (y, por lo tanto, se consumen) y se transfieren (atómicamente) a los parámetros de la continuación. [6]
En la mayoría de los casos, el orden de las llamadas sincrónicas no está garantizado por razones de rendimiento. Finalmente, durante la partida, los mensajes disponibles en la cola podrían ser robados por algún hilo interviniente; de hecho, el hilo despertado podría tener que esperar nuevamente. [7]
El cálculo π pertenece a la familia de cálculos de procesos , permite formalismos matemáticos para describir y analizar propiedades de computación concurrente mediante el uso de nombres de canales que se comunican a lo largo de los propios canales, y de esta manera es capaz de describir cálculos concurrentes cuya configuración de red puede cambiar durante el cálculo.
Los patrones de unión aparecieron por primera vez en el cálculo de unión fundacional de Fournet y Gonthier, un álgebra de procesos asincrónicos diseñado para una implementación eficiente en un entorno distribuido. [8] El cálculo de unión es un cálculo de procesos tan expresivo como el cálculo π completo . Fue desarrollado para proporcionar una base formal para el diseño de lenguajes de programación distribuida y, por lo tanto, evita intencionalmente las construcciones de comunicaciones que se encuentran en otros cálculos de procesos, como las comunicaciones de encuentro .
Join-Calculus es a la vez un cálculo de paso de nombres y un lenguaje central para la programación concurrente y distribuida. [9] Es por eso que Distributed Join-Calculus [10] basado en Join-Calculus con la programación distribuida fue creado en 1996. Este trabajo utiliza los agentes móviles donde los agentes no son solo programas sino imágenes centrales de procesos en ejecución con sus capacidades de comunicación.
JoCaml [11] [12] y Funnel [13] [14] son lenguajes funcionales que admiten patrones de unión declarativos. Presentan ideas para implementar directamente cálculos de procesos en un entorno funcional.
Otras extensiones de Java (no genéricas), JoinJava, fueron propuestas independientemente por von Itzstein y Kearney. [15]
Cardelli, Benton y Fournet propusieron una versión orientada a objetos de patrones de unión para C# llamada C# polifónico . [16]
Cω es una adaptación del cálculo de unión a un entorno orientado a objetos. [17] Esta variante de C# polifónico se incluyó en la versión pública de Cω (también conocido como Comega) en 2004.
Scala Joins es una biblioteca para utilizar Join-Pattern con Scala en el contexto de coincidencia de patrones extensible con el fin de integrar uniones en un marco de concurrencia basado en actores existente. [18]
Erlang es un lenguaje que soporta de forma nativa el paradigma concurrente, en tiempo real y distribuido. La concurrencia entre procesos era compleja, por eso el proyecto construyó un nuevo lenguaje, JErlang ( J significa Join ) basado en el cálculo de Join.
"Los patrones de unión se pueden utilizar para codificar fácilmente expresiones de concurrencia relacionadas, como actores y objetos activos". [19]
clase SymmetricBarrier { público de solo lectura Sincrónico . Canal Llegar ; public SymmetricBarrier ( int n ) { // Crea j y canales de inicialización (eliminados) var pat = j . When ( Llegar ); for ( int i = 1 ; i < n ; i ++ ) pat = pat . And ( Llegar ); pat . Do (() => { }); } }
var j = Join . Create (); Sincrónico . Channel [] hambriento ; Asincrónico . Channel [] palillo ; j . Init ( fuera hambriento , n ); j . Init ( fuera palillo , n ); para ( int i = 0 ; i < n ; i ++ ) { var izquierda = palillo [ i ]; var derecha = palillo [ ( i + 1 ) % n ]; j . Cuando ( hambriento [ i ]). Y ( izquierda ). Y ( derecha ). Hacer (() => { comer (); izquierda (); derecha (); // reemplazar palillos }); }
clase Lock { público readonly Synchronous . Channel Acquire ; público readonly Asynchronous . Channel Release ; public Lock () { // Crea j y canales de inicialización (eliminados) j . When ( Acquire ). And ( Release ). Do (() => { }); Release (); // inicialmente libre } }
clase Buffer < T > { público de solo lectura Asíncrono . Canal < T > Put ; público de solo lectura Síncrono < T > . Canal Get ; public Buffer () { Join j = Join . Create (); // asignar un objeto Join j . Init ( out Put ); // enlazar sus canales j . Init ( out Get ); j . When ( Get ). And ( Put ). Do // registrar chord ( t => { return t ; }); } }
clase ReaderWriterLock { privado de solo lectura Asíncrono . Canal inactivo ; privado de solo lectura Asíncrono . Canal < int > compartido ; público de solo lectura Síncrono . Canal AcqR , AcqW , RelR , RelW ; public ReaderWriterLock () { // Crea j y canales de inicialización (eliminados) j . When ( AcqR ). And ( idle ). Do (() => shared ( 1 )); j . When ( AcqR ). And ( shared ). Do ( n => shared ( n + 1 )); j . When ( RelR ). And ( shared ). Do ( n => { if ( n == 1 ) { idle (); } else { shared ( n - 1 ); } }); j . When ( AcqW ). And ( idle ). Do (() => { }); j . When ( RelW ). Do (() => idle ()); idle (); // inicialmente libre } }
clase Semáforo { público readonly Sincrónico . Canal Adquirir ; público readonly Asíncrono . Canal Liberar ; public Semaphore ( int n ) { // Crea j y canales de inicialización (eliminados) j . When ( Adquirir ). And ( Liberar ). Hacer (() => { }); for (; n > 0 ; n -- ) Liberar (); // inicialmente n libre } }
Un agente móvil es un agente de software autónomo con cierta capacidad social y, lo más importante, movilidad. Está compuesto por software y datos informáticos que pueden moverse entre diferentes ordenadores de forma automática mientras continúan sus ejecuciones.
Los agentes móviles pueden utilizarse para hacer coincidir la concurrencia y la distribución si se utiliza el cálculo de unión. Por eso se creó un nuevo concepto llamado "cálculo de unión distribuido"; es una extensión del cálculo de unión con ubicaciones y primitivas para describir la movilidad. Esta innovación utiliza a los agentes como procesos en ejecución con sus capacidades de comunicación para permitir una idea de la ubicación, que es un sitio físico que expresa la posición real del agente. Gracias al cálculo de unión, una ubicación se puede mover atómicamente a otro sitio. [24]
Los procesos de un agente se especifican como un conjunto que define su funcionalidad, incluida la emisión asincrónica de un mensaje y la migración a otra ubicación. En consecuencia, las ubicaciones se organizan en un árbol para representar más fácilmente el movimiento del agente. Con esta representación, un beneficio de esta solución es la posibilidad de crear un modelo simple de falla. Por lo general, una falla de un sitio físico causa la falla permanente de todas sus ubicaciones. Pero con el cálculo de unión, se puede detectar un problema con una ubicación en cualquier otra ubicación en ejecución, lo que permite la recuperación de errores. [24]
Por lo tanto, el cálculo de unión es el núcleo de un lenguaje de programación distribuido. En particular, la semántica operacional es fácilmente implementable en un entorno distribuido con fallas. Por lo tanto, el cálculo de unión distribuido trata los nombres de los canales y los nombres de las ubicaciones como valores de primera clase con alcances léxicos. Una ubicación controla sus propios movimientos y solo puede moverse hacia una ubicación cuyo nombre ha recibido. Esto proporciona una base sólida para el análisis estático y para la movilidad segura. Esto es completo para expresar configuraciones distribuidas. Sin embargo, en ausencia de fallas, la ejecución de procesos es independiente de la distribución. Esta transparencia de ubicación es esencial para el diseño de agentes móviles y muy útil para verificar sus propiedades. [24]
En 2007, se ha presentado una extensión del cálculo de uniones básico con métodos que hacen que los agentes sean proactivos. Los agentes pueden observar un entorno compartido entre ellos. Con este entorno, es posible definir variables compartidas con todos los agentes (por ejemplo, un servicio de nombres para descubrir agentes entre ellos). [25]
Los lenguajes join se construyen sobre el cálculo join, que se toma como lenguaje central. De modo que todo el cálculo se analiza con procesos asincrónicos y el patrón join proporciona un modelo para sincronizar el resultado. [9]
Para ello, existen dos compiladores:
Estos dos compiladores trabajan con el mismo sistema, un autómata.
sea A(n) | B() = P(n)y A(n) | C() = Q(n);;
Representa el consumo de un mensaje que llega a un modelo de unión completado. Cada estado es un posible paso para la ejecución del código y cada transición es la recepción de un mensaje para cambiar entre dos pasos. Y así, cuando se obtienen todos los mensajes, el compilador ejecuta el código de unión del cuerpo correspondiente a la unión del modelo completada.
En el cálculo de unión, los valores básicos son los nombres, como en el ejemplo A, B o C. Por lo tanto, los dos compiladores representan estos valores de dos maneras.
El compilador de unión usa un vector con dos ranuras, la primera para el nombre en sí y la segunda para una cola de mensajes pendientes.
Jocaml usa el nombre como un puntero a las definiciones. Estas definiciones almacenan los otros punteros de los otros nombres con un campo de estado y una estructura de fecha coincidente por mensaje.
La diferencia fundamental es cuando se ejecuta el proceso de protección, para el primero, se verifica si todos los nombres son los mensajes pendientes listos, mientras que el segundo usa solo una variable y accede a las otras para saber si el modelo está completo. [9]
Investigaciones recientes describen el esquema de compilación como la combinación de dos pasos básicos: envío y reenvío. El diseño y la corrección del despachador se derivan esencialmente de la teoría de coincidencia de patrones, mientras que la inserción de un paso de reenvío interno en las comunicaciones es una idea natural, que intuitivamente no cambia el comportamiento del proceso. Hicieron la observación de que lo que vale la pena observar es que una implementación directa de la coincidencia de patrones de unión extendida en el nivel de tiempo de ejecución complicaría significativamente la gestión de las colas de mensajes, que luego necesitarían ser escaneadas en busca de mensajes coincidentes antes de consumirlos. [26]
Existen muchos usos de los patrones de unión en diferentes lenguajes. Algunos lenguajes utilizan patrones de unión como base de sus implementaciones, por ejemplo, Polyphonic C# o MC#, pero otros lenguajes integran patrones de unión mediante una biblioteca como Scala Joins [27] para Scala o la biblioteca Joins para VB. [28] Además, el patrón de unión se utiliza a través de algunos lenguajes como Scheme para actualizar el patrón de unión. [29]
Jerlang | C.B. | Se une a la biblioteca | Do# polifónico | C# paralelo | Cω | Scala se une | F# | Esquema | Únase a Java | Hume | JoCaml | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Patrones a juego | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí | Sí |
Programador entre patrones de unión | Sí: primer partido | Sí: primera ronda/todos contra todos | Sí | Sí | Sí | Sí | Sí | Sí | No | Sí: aleatorio | Sí: primera ronda/todos contra todos | Sí: aleatorio |
Genéricos | Sí | — | Sí | No | — | No | Sí | Sí | No | No | No | No |
Primordial | No | Sí | — | — | — | Sí | Sí | Sí | No | Sí | No | No |
Join Java [30] es un lenguaje basado en el lenguaje de programación Java que permite el uso del cálculo de uniones. Introduce tres nuevas construcciones de lenguaje:
Ejemplo:
clase JoinExample { int fragment1 () & fragment2 ( int x ) { // Devolverá el valor de x al llamador de fragment1 return x ; } }
Ejemplo:
clase ThreadExample { señal thread ( SomeObject x ) { // Este código se ejecutará en un nuevo hilo } }
Los fragmentos de unión se pueden repetir en múltiples patrones de unión, por lo que puede haber un caso en el que se completen múltiples patrones de unión cuando se llama a un fragmento. Tal caso podría ocurrir en el ejemplo siguiente si se llaman B(), C() y D() y luego A(). El fragmento A() final completa tres de los patrones, por lo que hay tres métodos posibles que se pueden llamar. El modificador de clase ordenado se utiliza aquí para determinar qué método de unión se llamará. El valor predeterminado y cuando se usa el modificador de clase desordenado es elegir uno de los métodos al azar. Con el modificador ordenado, los métodos se priorizan según el orden en que se declaran.
Ejemplo:
clase ordenada SimpleJoinPattern { void A () & B () { } void A () & C () { } void A () & D () { } señal D () & E () { } }
El lenguaje más cercano relacionado es el polifónico C# .
En la codificación Erlang, la sincronización entre múltiples procesos no es sencilla. Por eso se creó JErlang, [31] una extensión de Erlang , The J is for Join. De hecho, para superar esta limitación se implementó JErlang, una extensión de Erlang inspirada en el cálculo de uniones . Las características de este lenguaje son:
operación () -> recibir { ok , suma } y { val , X } y { val , Y } -> { suma , X + Y }; { ok , mult } y { val , X } y { val , Y } -> { mult , X * Y }; { ok , sub } y { val , X } y { val , Y } -> { sub , X - Y }; fin fin
recibir { Transacción , M } y { límite , Inferior , Superior } cuando ( Inferior <= M y M <= Superior ) -> commit_transaction ( M , Transacción ) fin
recibir { obtener , X } y { establecer , X } -> { encontrado , 2 , X } fin ... recibir { Pin , id } y { auth , Pin } y { commit , Id } -> perform_transaction ( Pin , Id ) fin
recibir prop ({ sesión , Id }) y { acto , Acción , Id } -> realizar_acción ( Acción , Id ); { sesión , Id } y { cierre de sesión , Id } -> cierre de sesión_usuario ( Id ) fin ... recibir { Pin , id } y { auth , Pin } y { commit , Id } -> realizar_transacción ( Pin , Id ) fin
recibir { aceptar , Pid1 } y { asíncrono , Valor } y { aceptar , Pid2 } -> Pid1 ! { ok , Valor }, Pid2 ! { ok , Valor } fin
Yigong Liu ha escrito algunas clases para el patrón de unión que incluye todas las herramientas útiles como canales asincrónicos y sincrónicos, acordes , etc. Está integrado en el proyecto Boost c++.
plantilla < typename V > clase buffer : public joint { public : async < V > put ; synch < V , void > get ; buffer () { acorde ( get , put , & buffer :: chord_body ); } V cuerpo_acorde ( void_t g , V p ) { return p ; } };
Este ejemplo nos muestra un buffer seguro para subprocesos y una cola de mensajes con las operaciones básicas put y get. [32]
Polyphonic C# es una extensión del lenguaje de programación C#. Introduce un nuevo modelo de concurrencia con métodos sincrónicos y asincrónicos (que devuelven el control al autor de la llamada) y acordes (también conocidos como "patrones de sincronización" o "patrones de unión").
clase pública Buffer { pública String get () y pública async put ( String s ) { return s ; } }
Este es un ejemplo de buffer simple. [33]
El lenguaje MC# es una adaptación del lenguaje polifónico C# para el caso de cálculos distribuidos concurrentes.
manejador público Get2 long () y canal c1 ( long x ) y canal c2 ( long y ) { return ( x + y ); }
Este ejemplo demuestra el uso de acordes como herramienta de sincronización.
Parallel C# se basa en C# polifónico y agrega algunos conceptos nuevos como métodos móviles y funciones de orden superior.
usando Sistema ; clase Test13 { int Recibir () y async Enviar ( int x ) { devolver x * x ; } public static void Main ( string [ ] args ) { Test13 t = new Test13 ( ); t.Send ( 2 ) ; Console.WriteLine ( t.Receive ( ) ) ; } }
Este ejemplo demuestra cómo utilizar uniones. [34]
Cω agrega nuevas características del lenguaje para soportar la programación concurrente (basada en el anterior Polyphonic C# ). La biblioteca de concurrencia de uniones para C# y otros lenguajes .NET se deriva de este proyecto. [35] [36]
Es una biblioteca de patrones de unión declarativa y escalable fácil de usar. A diferencia de la biblioteca Russo, [28] no tiene bloqueo global. De hecho, funciona con un sistema de mensajes Atomic y CAS de comparación e intercambio . La biblioteca [37] utiliza tres mejoras para el patrón de unión:
JoCaml es el primer lenguaje en el que se implementó el patrón join. De hecho, al principio todas las diferentes implementaciones se compilaron con el compilador JoCaml. El lenguaje JoCaml es una extensión del lenguaje OCaml . Amplía OCaml con soporte para concurrencia y sincronización, la ejecución distribuida de programas y la reubicación dinámica de fragmentos de programas activos durante la ejecución. [38]
tipo monedas = Nickel | Dime y bebidas = Café | Té y botones = BCoffee | BTea | BCancelar ;;(* def define una cláusula de conjunto de patrón Join * "&" en el lado izquierdo de = significa join (sincronismo de canal) * "&" en el lado derecho significa: proceso paralelo * synchronous_reply :== "reply" [x] "to" channel_name * los canales sincrónicos tienen tipos similares a funciones (`a -> `b) * los canales asincrónicos tienen tipos (`a Join.chan) * solo la última declaración en una expresión rhs de patrón puede ser un mensaje asincrónico * 0 en una posición de mensaje asincrónico significa STOP ("no se envió mensaje" en la terminología de CSP). *)def put ( s ) = print_endline s ; 0 (* DETENER *) ;; (* put: cadena Join.chan *)def serve ( beber ) = match drink con Coffee -> put ( "Café" ) | Tea -> put ( "Tea" ) ;; (* serve: bebidas Join.chan *)def reembolso ( v ) = let s = Printf.sprintf " Reembolso %d" v en put ( s ) ;; (* reembolso: int Join.chan * ) deje que new_vending sirva reembolso = deje que vend ( costo : int ) ( crédito : int ) = si crédito >= costo entonces ( verdadero , crédito - costo ) de lo contrario ( falso , crédito ) en def moneda ( Níquel ) y valor ( v ) = valor ( v + 5 ) y respuesta () a moneda o moneda ( Dime ) y valor ( v ) = valor ( v + 10 ) y respuesta () a moneda o botón ( BCoffee ) & valor ( v ) = let should_serve , resto = vend 10 v en ( si should_serve entonces serve ( Coffee ) de lo contrario 0 (* STOP *) ) & valor ( resto ) & responder () al botón o botón ( BTea ) y valor ( v ) = let should_serve , resto = vend 5 v en ( si should_serve entonces serve ( Tea ) de lo contrario 0 (*STOP *) ) y valor ( resto ) y responder () al botón o botón ( BCancelar ) y valor ( v ) = reembolso ( v ) y valor ( 0 ) y responder () al botón en el valor de generación ( 0 ) ; moneda , botón (* moneda, botón: int -> unidad *) ;; (* new_vending: bebida Join.chan -> int Join.chan -> (int->unidad)*(int->unidad) *) deje que ccoin , cbutton = new_vending sirva el reembolso en ccoin ( Nickel ); ccoin ( Nickel ); ccoin ( Dime ); Unix.sleep ( 1 ); cbutton ( BCoffee ); Unix.sleep ( 1 ) ; cbutton ( BTea ); Unix.sleep ( 1 ) ; cbutton ( BCancel ); Unix.sleep ( 1 ) ( * deje que se muestre el último mensaje * ) ;;
da
CaféTéReembolso 5
Hume [39] es un lenguaje funcional estricto y fuertemente tipado para plataformas de recursos limitados, con concurrencia basada en paso de mensajes asincrónico, programación de flujo de datos y una sintaxis similar a Haskell .
Hume no proporciona mensajería sincrónica.
Envuelve un conjunto de patrones de unión con un canal en común como una caja , enumerando todos los canales en una tupla de entrada y especificando todas las salidas posibles en una tupla de salida .
Cada patrón de unión en el conjunto debe cumplir con el tipo de tupla de entrada del cuadro , especificando un '*' para los canales no requeridos, dando una expresión cuyo tipo se ajuste a la tupla de salida, marcando '*' las salidas no alimentadas.
Una cláusula de cable especifica
Un cuadro puede especificar controladores de excepciones con expresiones que se ajusten a la tupla de salida.
datos Monedas = Nickel | Dime ; datos Bebidas = Café | Té ; datos Botones = BCoffee | BTea | BCancelar ; tipo Int = int 32 ; tipo String = string ; mostrar u = u como string ; caja de café en ( moneda :: Monedas , botón :: Botones , valor :: Int ) - canales de entrada salida ( bebida_salida :: String , valor ' :: Int , reembolso_salida :: String ) - salidas con nombre match -- * comodines para salidas no completadas y entradas no consumidas ( Nickel , * , v ) -> ( * , v + 5 , * ) | ( Dime , * , v ) -> ( * , v + 10 , * ) | ( * , BCoffee , v ) -> vend Coffee 10 v | ( * , BTea , v ) -> vend Tea 5 v | ( * , BCancel , v ) -> let refund u = "Refund " ++ show u ++ " \n " in ( * , 0 , refund v ) ; vender bebida costo crédito = si crédito >= costo entonces ( servir bebida , crédito - costo , * ) de lo contrario ( * , crédito , * ); servir bebida = caso bebida de Café -> "Café \n " Té -> "Té \n " ; control de caja en ( c :: char ) salida ( moneda :: Monedas , botón :: Botones ) coincidencia 'n' -> ( Níquel , * ) | 'd' -> ( Diez centavos , * ) | 'c' -> ( * , BCoffee ) | 't' -> ( * , BTea ) | 'x' -> ( * , BCancelar ) | _ -> ( * , * ) ; transmitir console_outp a "std_out" ; transmitir console_inp desde "std_in" ; -- cableado de flujo de datoscable café -- entradas (canal orígenes) ( control . moneda , control . botón , café . valor ' inicialmente 0 ) -- salidas destinos ( console_outp , café . valor , console_outp ) ; control de cable ( console_inp ) ( café . moneda , café . botón ) ;
Una extensión de Visual Basic 9.0 con construcciones de concurrencia asincrónica, llamada Concurrent Basic (CB para abreviar), ofrece los patrones de unión. CB (se basa en trabajos anteriores sobre Polyphonic C#, Cω y la biblioteca de uniones) adopta una sintaxis simple similar a un evento familiar para los programadores de VB, permite declarar abstracciones de concurrencia genéricas y proporciona un soporte más natural para la herencia, lo que permite que una subclase aumente el conjunto de patrones. La clase CB puede declarar un método para ejecutar cuando se ha producido una comunicación en un conjunto particular de canales locales asincrónicos y sincrónicos, formando un patrón de unión. [28]
Módulo de búfer Put público asíncrono ( ByVal s como cadena ) Take público síncrono () como cadena Función privada CaseTakeAndPut ( ByVal s As String ) Como String _ Cuando Take , Put Devuelve s Fin de la función Módulo final
Este ejemplo muestra todas las palabras clave nuevas utilizadas por Concurrent Basic: Asynchronous, Synchronous y When. [40]
Esta biblioteca es una abstracción de alto nivel del patrón Join que utiliza objetos y genéricos. Los canales son valores delegados especiales de algún objeto Join común (en lugar de métodos). [41]
clase Buffer { público de solo lectura Asíncrono . Canal < cadena > Put ; público de solo lectura Síncrono < cadena > . Canal Get ; public Buffer () { Unir join = Join . Create (); join . Initialize ( salida Poner ); join . Initialize ( salida Obtener ); join . When ( Obtener ). And ( Poner ). Do ( delegado ( cadena s ) { devolver s ; }); } }
Este ejemplo muestra cómo utilizar los métodos del objeto Join. [42]
La biblioteca Scala Joins utiliza el patrón Join. Las funciones de comparación de patrones de este lenguaje se han generalizado para permitir la independencia de representación de los objetos utilizados en la comparación de patrones. Por lo tanto, ahora es posible utilizar un nuevo tipo de abstracción en las bibliotecas. [ Aclaración necesaria ] La ventaja de los patrones de combinación es que permiten una especificación declarativa de la sincronización entre diferentes subprocesos. A menudo, los patrones de combinación se corresponden estrechamente con una máquina de estados finitos que especifica los estados válidos del objeto.
En Scala, es posible resolver muchos problemas con la coincidencia de patrones y Scala Joins, por ejemplo el Lector-Escritor. [27]
clase ReaderWriterLock extiende Joins { privado val Sharing = new AsyncEvent [ Int ] val Exclusive , ReleaseExclusive = new NullarySyncEvent val Shared , ReleaseShared = new NullarySyncEvent join { caso Exclusive () y Sharing ( 0 ) => Respuesta exclusiva caso ReleaseExclusive () => { Sharing ( 0 ); Respuesta ReleaseExclusive } caso Shared () y Sharing ( n ) => { Sharing ( n + 1 ); Respuesta compartida } caso ReleaseShared () y Sharing ( 1 ) => { Sharing ( 0 ); Respuesta ReleaseShared } caso ReleaseShared () y Sharing ( n ) => { Sharing ( n - 1 ); Respuesta ReleaseShared } } Sharing ( 0 ) }
Con una clase declaramos eventos en campos regulares. Por lo tanto, es posible utilizar la construcción Join para habilitar una coincidencia de patrones a través de una lista de declaraciones de casos. Esa lista se representa mediante => con una parte de la declaración en cada lado. El lado izquierdo es un modelo del patrón de unión para mostrar la combinación de eventos asincrónicos y sincrónicos y el lado derecho es el cuerpo de la unión que se ejecuta cuando se completa el modelo de unión.
En Scala, también es posible utilizar la biblioteca de actores de Scala [43] con el patrón de unión. Por ejemplo, un búfer sin límites: [27]
val Put = new Join1 [ Int ] val Get = new Join clase Buffer extiende JoinActor { def act () { recibir { caso Get () y Put ( x ) => Obtener respuesta x } } }
Scala Join y Chymyst son implementaciones más nuevas del patrón Join, que mejoran los Scala Joins del Dr. Philipp Haller.
Join Language es una implementación del patrón Join en Haskell.
Los patrones de unión permiten un nuevo tipo de programación, especialmente para las arquitecturas multinúcleo disponibles en muchas situaciones de programación con altos niveles de abstracción. Esto se basa en los patrones de protección y propagación. Por lo tanto, un ejemplo de esta innovación se ha implementado en Scheme. [29]
Los protectores son esenciales para garantizar que solo se actualicen o recuperen los datos con una clave coincidente. La propagación puede cancelar un elemento, leer su contenido y volver a colocar un elemento en un almacén. Por supuesto, el elemento también está en el almacén durante la lectura. Los protectores se expresan con variables compartidas. Y, por lo tanto, la novedad es que el patrón de unión ahora puede contener partes propagadas y simplificadas. Por lo tanto, en Scheme, la parte anterior a / se propaga y la parte posterior a / se elimina. El uso de Goal-Based es dividir el trabajo en muchas tareas y unir todos los resultados al final con el patrón de unión. Se ha implementado un sistema llamado "MiniJoin" para usar el resultado intermedio para resolver las otras tareas si es posible. Si no es posible, espera la solución de las otras tareas para resolverse a sí mismo.
Por lo tanto, la aplicación del patrón de unión concurrente ejecutada en paralelo en una arquitectura de múltiples núcleos no garantiza que la ejecución paralela genere conflictos. Para garantizar esto y un alto grado de paralelismo, se utiliza una memoria transaccional de software (STM) dentro de una estructura de datos concurrentes altamente ajustada basada en comparación e intercambio atómico (CAS). Esto permite ejecutar muchas operaciones concurrentes en paralelo en una arquitectura multinúcleo. Además, se utiliza una ejecución atómica para evitar el "falso conflicto" entre CAS y STM. [29]
Join Pattern no es el único patrón para realizar multitareas pero es el único que permite la comunicación entre recursos, la sincronización y la unión de diferentes procesos.