驴D贸nde est谩n las patas del modelo de memoria Java?

El hardware y los compiladores modernos est谩n listos para cambiar nuestro c贸digo, si solo funciona m谩s r谩pido. Y sus fabricantes esconden cuidadosamente su cocina interior. Y todo est谩 bien siempre que el c贸digo se ejecute en un hilo.

En un entorno de subprocesos m煤ltiples, puede forzosamente observar cosas interesantes. Por ejemplo, la ejecuci贸n de las instrucciones del programa no est谩 en el orden que est谩 escrito en el c贸digo fuente. De acuerdo, es desagradable darse cuenta de que ejecutar el c贸digo fuente l铆nea por l铆nea es solo nuestra imaginaci贸n.

Pero todos ya se han dado cuenta, porque de alguna manera hay que vivir con eso. Y los programadores de Java incluso viven bien. Debido a que Java tiene un modelo de memoria, el Modelo de Memoria Java (JMM), que proporciona reglas bastante simples para escribir el c贸digo correcto de subprocesos m煤ltiples.

Y estas reglas son suficientes para la mayor铆a de los programas. Si no los conoce, pero escribe o desea escribir programas multiproceso en Java, entonces es mejor familiarizarse con ellos lo antes posible. Y si lo sabes, pero no tienes suficiente contexto o es interesante saber de d贸nde crecen las piernas de JMM, entonces este art铆culo puede ayudarte.

Y persiguiendo la abstracci贸n


En mi opini贸n, hay un pastel o, m谩s convenientemente, un iceberg. JMM es la punta del iceberg. El iceberg en s铆 es una teor铆a de la programaci贸n multiproceso bajo el agua. Debajo del iceberg est谩 el infierno.



Un iceberg es una abstracci贸n; si se filtra, ciertamente veremos el infierno. Aunque est谩n sucediendo muchas cosas interesantes all铆, en el art铆culo de revisi贸n no llegaremos a esto.

En el art铆culo, estoy m谩s interesado en los siguientes temas:

  • Teoria y Terminologia
  • 驴C贸mo se refleja la teor铆a de la programaci贸n multiproceso en JMM?
  • Modelos de programaci贸n competitiva

La teor铆a de la programaci贸n multiproceso le permite alejarse de la complejidad de los procesadores y compiladores modernos, le permite simular la ejecuci贸n de programas multiproceso y estudiar sus propiedades. Roman Elizarov hizo un excelente informe , cuyo prop贸sito es proporcionar una base te贸rica para comprender JMM. Recomiendo el informe a todos los que est茅n interesados 鈥嬧媏n este tema.

驴Por qu茅 es importante conocer la teor铆a? En mi opini贸n, espero solo para los m铆os, algunos programadores tienen la opini贸n de que JMM es una complicaci贸n del lenguaje y la reparaci贸n de algunos problemas de plataforma con subprocesos m煤ltiples. La teor铆a muestra que Java no complic贸, sino que simplific贸 y convirti贸 la programaci贸n multiproceso m谩s compleja y predecible.

Competencia y concurrencia


Primero, veamos la terminolog铆a. Desafortunadamente, no hay consenso en la terminolog铆a: al estudiar diferentes materiales, puede encontrar diferentes definiciones de competencia y concurrencia.

El problema es que incluso si llegamos al fondo de la verdad y encontramos las definiciones exactas de estos conceptos, todav铆a no vale la pena esperar que todos signifiquen lo mismo con estos conceptos. No encontrar谩s los extremos aqu铆.

Roman Elizarov, en un informe, la teor铆a de la programaci贸n paralela para profesionales sugiere que a veces estos conceptos son mixtos. La programaci贸n paralela a veces se distingue como un concepto general que se divide en competitivo y distribuido.

Me parece que en el contexto de JMM todav铆a necesita separar la competencia y el paralelismo, o m谩s bien incluso comprender que hay dos paradigmas diferentes, sin importar c贸mo se llamen.

A menudo citado por Rob Pike, quien distingue entre conceptos de la siguiente manera:

  • La competencia es una forma de resolver simult谩neamente muchos problemas.
  • La concurrencia es una forma de realizar diferentes partes de una sola tarea.

La opini贸n de Rob Pike no es un est谩ndar, pero en mi opini贸n, es conveniente aprovecharla para seguir estudiando el tema. Lea m谩s sobre las diferencias aqu铆 .

Lo m谩s probable es que aparezca una mayor comprensi贸n del problema si destacamos las caracter铆sticas principales de un programa competitivo y paralelo. Hay muchos signos, considere los m谩s significativos.

Se帽ales de competencia.

  • La presencia de varios flujos de control (por ejemplo, Thread en Java, corutina en Kotlin), si solo hay un flujo de control, entonces no puede haber una ejecuci贸n competitiva
  • Resultado no determinista. El resultado depende de eventos aleatorios, implementaci贸n y c贸mo se realiz贸 la sincronizaci贸n. Incluso si cada secuencia es completamente determinista, el resultado final ser谩 no determinista

Un programa paralelo tendr谩 un conjunto diferente de caracter铆sticas.

  • Opcional tiene m煤ltiples flujos de control
  • Puede conducir a un resultado determinista, por ejemplo, el resultado de multiplicar cada elemento de la matriz por un n煤mero no cambiar谩 si lo multiplica en partes en paralelo

Curiosamente, la ejecuci贸n en paralelo es posible en un solo flujo de control, e incluso en una arquitectura de un solo n煤cleo. El hecho es que el paralelismo a nivel de tareas (o flujos de control) a los que estamos acostumbrados no es la 煤nica forma de realizar c谩lculos en paralelo.

La concurrencia es posible a nivel de:

  • bits (por ejemplo, en m谩quinas de 32 bits, la adici贸n se realiza en una acci贸n, procesando los 4 bytes de un n煤mero de 32 bits en paralelo)
  • instrucciones (en un n煤cleo, en un hilo, el procesador puede ejecutar instrucciones en paralelo, a pesar de que el c贸digo es secuencial)
  • datos (hay arquitecturas con procesamiento de datos paralelo (Datos m煤ltiples de instrucci贸n 煤nica) que pueden ejecutar una instrucci贸n en un conjunto de datos grande)
  • tareas (implica la presencia de m煤ltiples procesadores o n煤cleos)

La concurrencia a nivel de instrucci贸n es un ejemplo de optimizaciones que ocurren con la ejecuci贸n de c贸digo que est谩n ocultas para el programador.

Se garantiza que el c贸digo optimizado ser谩 equivalente al original dentro del marco de un hilo, porque es imposible escribir c贸digo adecuado y predecible si no hace lo que el programador pretend铆a.

No todo lo que se ejecuta en paralelo es importante para JMM. La ejecuci贸n concurrente en el nivel de instrucci贸n dentro de un solo hilo no se considera en JMM.

La terminolog铆a es muy inestable, con una presentaci贸n de Roman Elizarov llamada "Teor铆a de la programaci贸n paralela para profesionales", aunque hay m谩s sobre programaci贸n competitiva, si se atiene a lo anterior.

En el contexto de JMM, en el art铆culo me limitar茅 al t茅rmino competencia, ya que la competencia es a menudo sobre el estado general. Pero aqu铆 debe tener cuidado de no aferrarse a los t茅rminos, sino comprender que existen diferentes paradigmas.

Modelos con un estado com煤n: "rotaci贸n de operaciones" y "sucedi贸 antes"


En su art铆culo, Maurice Herlichi (autor de la programaci贸n The Art Of Multiprocessor) escribe que un sistema competitivo contiene una colecci贸n de procesos secuenciales (en trabajos te贸ricos significa lo mismo que un hilo) que se comunican a trav茅s de la memoria compartida.

El modelo de estado general incluye c谩lculos con mensajes, donde el estado compartido es una cola de mensajes y c谩lculos con memoria compartida, donde el estado com煤n son estructuras en la memoria.

Cada uno de los c谩lculos puede ser simulado.

El modelo se basa en una m谩quina de estados finitos. El modelo se centra exclusivamente en el estado compartido y los datos locales de cada uno de los flujos se ignoran por completo. Cada acci贸n de flujos sobre un estado compartido es una funci贸n de la transici贸n a un nuevo estado.

Entonces, por ejemplo, si 4 hilos escriben datos en una variable compartida, entonces habr谩 4 funciones para la transici贸n a un nuevo estado. Cu谩l de estas funciones se aplicar谩 depende de la cronolog铆a de los eventos en el sistema.

Los c谩lculos de paso de mensajes se modelan de manera similar, solo el estado y las funciones de transici贸n dependen del env铆o o recepci贸n de mensajes.

Si el modelo le pareci贸 complicado, en el ejemplo lo arreglaremos. Es realmente muy simple e intuitivo. Tanto es as铆 que sin saber acerca de la existencia de este modelo, la mayor铆a de las personas a煤n analizar谩 el programa como sugiere el modelo.

Tal modelo se llama modelo de rendimiento a trav茅s de la alternancia de operaciones (el nombre se escuch贸 en un informe de Roman Elizarov).

En la intuici贸n y naturalidad, puede anotar con seguridad las ventajas del modelo. Puede entrar en la naturaleza con las palabras clave Consistencia secuencial y el trabajo de Leslie Lamport.

Sin embargo, hay una aclaraci贸n importante sobre este modelo. El modelo tiene la limitaci贸n de que todas las acciones en un estado compartido deben ser instant谩neas y, al mismo tiempo, las acciones no pueden ocurrir simult谩neamente. Dicen que dicho sistema tiene un orden lineal : todas las acciones en el sistema est谩n ordenadas.

En la pr谩ctica, esto no sucede. La operaci贸n no ocurre instant谩neamente, sino que se realiza en un intervalo; en los sistemas de m煤ltiples n煤cleos, estos intervalos pueden cruzarse. Por supuesto, esto no significa que el modelo sea in煤til en la pr谩ctica, solo necesita crear ciertas condiciones para su uso.

Mientras tanto, considere otro modelo: "sucedi贸 antes", que se centra no en el estado, sino en el conjunto de celdas de memoria de lectura y escritura durante la ejecuci贸n (historial) y sus relaciones.

El modelo dice que los eventos en diferentes flujos no son instant谩neos y at贸micos, sino paralelos, y que no es posible construir un orden entre ellos. Los eventos (escritura y lectura de datos compartidos) en flujos en una arquitectura multiprocesador o multin煤cleo ocurren realmente en paralelo. No existe un concepto de tiempo global en el sistema, no podemos entender cu谩ndo termin贸 una operaci贸n y comenz贸 otra.

En la pr谩ctica, esto significa que podemos escribir un valor en una variable en un hilo y hacerlo, por ejemplo, en la ma帽ana, y leer el valor de esta variable en otro hilo en la noche, y no podemos decir que seguro leeremos el valor escrito en la ma帽ana. En teor铆a, estas operaciones tienen lugar en paralelo y no est谩 claro cu谩ndo terminar谩 una y comenzar谩 otra operaci贸n.

Es dif铆cil imaginar c贸mo resulta que las operaciones simples de lectura y escritura realizadas en diferentes momentos del d铆a tienen lugar simult谩neamente. Pero si lo piensa, realmente no nos importa cu谩ndo ocurren los eventos de escritura y lectura, si no podemos garantizar que veremos el resultado de la grabaci贸n.

Y realmente no podemos ver el resultado de la grabaci贸n, es decir en una variable cuyo valor es 0 en la secuencia P, escribimos 1 , y en la secuencia Q leemos esta variable. No importa cu谩nto tiempo f铆sico pase despu茅s de la grabaci贸n, a煤n podemos leer 0 .

As铆 es como funcionan las computadoras y el modelo lo refleja.

El modelo es completamente abstracto y necesita una visualizaci贸n conveniente para un trabajo conveniente. Para la visualizaci贸n y solo para ello, se utiliza un modelo con tiempo global, con reservas de que al probar las propiedades de los programas, no se utiliza el tiempo global. En la visualizaci贸n, cada evento se representa como un intervalo con un principio y un final.

Los eventos tienen lugar en paralelo, como descubrimos. Pero a煤n as铆, el sistema tiene un orden parcial , ya que hay pares especiales de eventos que tienen un orden, en cuyo caso dicen que estos eventos tienen una relaci贸n "sucedi贸 antes". Si escuchas por primera vez acerca de la relaci贸n "sucedi贸 antes", entonces probablemente saber el hecho de que esta relaci贸n organiza los eventos no te ayudar谩 mucho.

Intentando analizar un programa Java


Consideramos un m铆nimo te贸rico, tratemos de seguir adelante y consideremos un programa multiproceso en un lenguaje espec铆fico: Java, a partir de dos hilos con un estado mutable com煤n.

Un ejemplo cl谩sico

private static int x = 0, y = 0; private static int a = 0, b = 0; synchronized (this) { a = 0; b = 0; x = 0; y = 0; } Thread p = new Thread(() -> { a = 1; x = b; }); Thread q = new Thread(() -> { b = 1; y = a; }); p.start(); q.start(); p.join(); q.join(); System.out.println("x=" + x + ", y=" + y); 

Necesitamos simular la ejecuci贸n de este programa y obtener todos los resultados posibles: los valores de las variables x e y. Habr谩 varios resultados, como recordamos de la teor铆a, tal programa no es determinista.

驴C贸mo vamos a modelar? Inmediatamente quiero usar el modelo de operaciones entrelazado. Pero el modelo "sucedi贸 antes" nos dice que los eventos en un hilo son paralelos a los eventos de otro hilo. Por lo tanto, el modelo de operaciones alternas aqu铆 no es apropiado si no existe una relaci贸n "ocurrida antes" entre las operaciones.

El resultado de la ejecuci贸n de cada subproceso siempre est谩 determinado, ya que los eventos en un subproceso siempre est谩n ordenados, considere que reciben una relaci贸n "sucedi贸 antes" de forma gratuita. Pero c贸mo los eventos en diferentes flujos pueden obtener la relaci贸n "sucedi贸 antes" no es del todo obvio. Por supuesto, esta relaci贸n se formaliza en el modelo, todo el modelo est谩 escrito en lenguaje matem谩tico. Pero qu茅 hacer con esto en la pr谩ctica, en un idioma en particular, no se entiende de inmediato.

Cuales son las opciones?

Ignorar restricciones y simular intercalaci贸n. Puedes probarlo, tal vez no pase nada malo.

Para comprender qu茅 tipo de resultados se pueden obtener, simplemente enumeramos todas las posibles variantes de ejecuci贸n.

Todas las ejecuciones de programas posibles se pueden representar como una m谩quina de estados finitos.



Cada c铆rculo es un estado del sistema, en nuestro caso las variables a, b, x, y . Una funci贸n de transici贸n es una acci贸n en un estado que coloca al sistema en un nuevo estado. Dado que dos flujos pueden realizar acciones en el estado general, habr谩 dos transiciones desde cada estado. Los c铆rculos dobles son los estados finales e iniciales del sistema.

En total, son posibles 6 ejecuciones diferentes, que resultan en pares de valores x, y:
(1, 1), (1, 0), (0, 1)



Podemos ejecutar el programa y verificar los resultados. Como corresponde a un programa competitivo, tendr谩 un resultado no determinista.

Para probar programas competitivos, es mejor usar herramientas especiales ( herramienta , informe ).

Pero puede intentar ejecutar el programa varios millones de veces, o incluso mejor, escribir un ciclo que lo haga por nosotros.

Si ejecutamos el c贸digo en una arquitectura de n煤cleo 煤nico o procesador 煤nico, entonces deber铆amos obtener el resultado del conjunto que esperamos. El modelo de rotaci贸n funcionar谩 bien. En la arquitectura multin煤cleo, por ejemplo x86, podemos sorprendernos con el resultado: podemos obtener el resultado (0,0), que no puede ser de acuerdo con nuestro modelo.

La explicaci贸n de esto se puede encontrar en Internet por la palabra clave: reordenamiento . Ahora es importante comprender que el modelado entrelazado realmente no es adecuado en una situaci贸n en la que no podemos determinar el orden de acceso al estado compartido .

Teor铆a de la programaci贸n competitiva y JMM


Es hora de echar un vistazo m谩s de cerca a la relaci贸n "sucedi贸 antes" y c贸mo se hace amigo de JMM. La definici贸n original de la relaci贸n "sucedi贸 antes" se puede encontrar en Hora, Relojes y Ordenaci贸n de eventos en un sistema distribuido.

El modelo de memoria de lenguaje ayuda a escribir c贸digo competitivo, ya que determina qu茅 operaciones est谩n relacionadas con "sucedi贸 antes". Una lista de tales operaciones se presenta en la especificaci贸n en la secci贸n Orden antes de que ocurra. De hecho, esta secci贸n responde a la pregunta: 驴bajo qu茅 condiciones veremos el resultado de la grabaci贸n en otra transmisi贸n?

Hay varios pedidos en JMM. Alexei Shipilev habla muy vigorosamente sobre las reglas en uno de sus informes .

En el modelo de tiempo global, todas las operaciones en el mismo hilo est谩n en orden. Por ejemplo, los eventos de escribir y leer una variable se pueden representar como dos intervalos, luego el modelo garantiza que estos intervalos nunca se crucen dentro del marco de una sola secuencia. En JMM, este orden se llama Orden de programa ( PO ).

PO vincula acciones en un solo hilo y no dice nada sobre el orden de ejecuci贸n, solo habla sobre el orden en el c贸digo fuente. Esto es suficiente para garantizar el determinismo para cada flujo por separado . PO puede considerarse como datos sin procesar. PO es siempre f谩cil de organizar en un programa: todas las operaciones (orden lineal) en el c贸digo fuente dentro de una sola secuencia tendr谩n PO .

En nuestro ejemplo, obtenemos algo como lo siguiente:

P: a = 1 PO x = b - escribir en ay leer b tiene orden de pedido
Q: b = 1 PO y = a - escribe en b y lee a tiene orden de pedido

Vi esta forma de escribir w (a, 1) PO r (b): 0. Realmente espero que nadie la haya patentado para informes. Sin embargo, la especificaci贸n tiene una forma similar.

Pero cada hilo individualmente no es particularmente interesante para nosotros, ya que los hilos tienen un estado com煤n, estamos m谩s interesados 鈥嬧媏n la interacci贸n de los flujos. Todo lo que queremos es asegurarnos de que veremos un registro de variables en otros hilos.

Perm铆tame recordarle que esto no funcion贸 para nosotros, porque las operaciones de escritura y lectura de variables en diferentes flujos no son instant谩neas (estos son segmentos que se cruzan), respectivamente, es imposible analizar d贸nde est谩n el comienzo y el final de las operaciones.

La idea es simple: en el momento en que leemos la variable a en la secuencia Q , el registro de esta misma variable en la secuencia P podr铆a no terminar todav铆a. Y no importa cu谩nto tiempo f铆sico compartan estos eventos: un nanosegundo o unas pocas horas.

Para ordenar eventos, necesitamos la relaci贸n "sucedi贸 antes". JMM define esta relaci贸n. La especificaci贸n corrige el orden en un hilo:

Si la operaci贸n x e y est谩n en el mismo hilo y en PO x ocurre primero, y luego y, entonces x sucedi贸 antes que y.


Mirando hacia el futuro, podemos decir que podemos reemplazar todas las OP con Happens-before ( HB ):

 P: w(a, 1) HB r(b) Q: w(b, 1) HB r(a) 

Pero nuevamente volvemos dentro del marco de una secuencia. HB es posible entre operaciones que ocurren en diferentes hilos, para tratar estos casos nos familiarizaremos con otras 贸rdenes.

Orden de sincronizaci贸n ( SO ): vincula las acciones de sincronizaci贸n ( SA ), se proporciona una lista completa de SA en la especificaci贸n, en la secci贸n 17.4.2. Acciones Aqu铆 hay algunos de ellos:

  • Lectura de variable vol谩til
  • Escribir variable vol谩til
  • Monitor de bloqueo
  • Desbloquear monitor

SO es interesante para nosotros, porque tiene la propiedad de que todas las lecturas en el orden SO ven las 煤ltimas entradas en SO . Y les recuerdo que solo lo estamos logrando.

En este lugar, repetir茅 lo que buscamos. Tenemos un programa multiproceso, queremos simular todas las ejecuciones posibles y obtener todos los resultados que puede dar. Hay modelos que permiten que esto se haga de manera bastante simple. Pero requieren que se ordenen todas las acciones en el estado compartido.

De acuerdo con la propiedad SO : si todas las acciones del programa son SA , alcanzaremos nuestro objetivo. Es decir Podemos establecer un modificador vol谩til para todas las variables y podemos usar el modelo de alternancia. Si la intuici贸n te dice que esto no vale la pena, entonces tienes toda la raz贸n. Con estas acciones, simplemente prohibimos las optimizaciones sobre el c贸digo, por supuesto, a veces esta es una buena opci贸n, pero definitivamente no es un caso general.

Considere otra orden Sincronizar con ( SW ): orden SO para desbloqueo / bloqueo espec铆fico, escritura / lectura de pares vol谩tiles. No importa en qu茅 flujos estar谩n estas acciones, lo principal es que est谩n en el mismo monitor, variable vol谩til. SW proporciona un puente entre hilos.

Y ahora llegamos al orden m谩s interesante: sucede antes ( HB ).
HB es un cierre transitivo de la uni贸n de SW y PO . PO da un orden lineal dentro de la secuencia, y SW proporciona un puente entre las secuencias. HB es transitivo, es decir si

 x HB y  y HB z,  x HB z 

La especificaci贸n tiene una lista de relaciones HB , puede familiarizarse con ella con m谩s detalle, aqu铆 hay algunas de la lista:

Dentro de un solo hilo, cualquier operaci贸n sucede antes que cualquier operaci贸n que la siga en el c贸digo fuente.

Sale de un bloque / m茅todo sincronizado antes de ingresar un bloque / m茅todo sincronizado en el mismo monitor.

Escribir un campo vol谩til sucede antes de leer el mismo campo vol谩til .

Volvamos a nuestro ejemplo:

 P: a = 1 PO x = b Q: b = 1 PO y = a 

Volvamos a nuestro ejemplo e intentemos analizar el programa, teniendo en cuenta los pedidos.

El an谩lisis del programa utilizando JMM se basa en presentar cualquier hip贸tesis y confirmarla o refutarla.



Comenzamos nuestro an谩lisis con la hip贸tesis de que ni una sola ejecuci贸n del programa da el resultado (0, 0). La ausencia de un resultado (0, 0) en todas las ejecuciones es una supuesta propiedad del programa.

Probamos la hip贸tesis construyendo diferentes ejecuciones.

Vi la nomenclatura aqu铆 (a veces aparece en lugar de palabra race con una flecha, Alexey mismo usa la flecha y la carrera de palabras en sus informes, pero advierte que este orden no existe en JMM y usa esta notaci贸n para mayor claridad).

Hacemos una peque帽a reserva.

Dado que todas las acciones sobre variables comunes son importantes para nosotros, y en el ejemplo, las variables comunes son a, b, x, y . Entonces, por ejemplo, la operaci贸n x = b debe considerarse como r (b) yw (x, b), r(b) HB w(x,b) (basada en PO ). Pero dado que la variable x no se lee en ninguna parte de los hilos (la lectura impresa al final del c贸digo no es interesante, porque despu茅s de la operaci贸n de uni贸n en el hilo veremos el valor x), no podemos considerar la acci贸n w (x, b).

Comprueba la primera actuaci贸n.

 w(a, 1) HB r(b): 0 鈥 w(b, 1) HB r(a): 0 

En la secuencia Q, leemos la variable a, escribimos en esta variable en la secuencia P. No hay orden entre escribir y leer (PO, SW, HB) .

Si la variable est谩 escrita en un hilo y la lectura est谩 en otro hilo y no hay una relaci贸n HB entre operaciones, entonces dicen que la variable se lee en carrera. Y en la carrera seg煤n JMM podemos leer el 煤ltimo valor registrado en HB o cualquier otro valor.

Tal actuaci贸n es posible. La ejecuci贸n no viola JMM . Al leer la variable a, puede ver cualquier valor, ya que la lectura ocurre bajo la carrera y no hay garant铆a de que veremos la acci贸n w (a, 1). Esto no significa que el programa funcione correctamente, simplemente significa que se espera ese resultado.

No tiene sentido considerar el resto de la ejecuci贸n, ya que la hip贸tesis ya est谩 destruida .

JMM dice que si el programa no tiene carreras de datos, entonces todas las ejecuciones pueden considerarse como secuenciales. Vamos a deshacernos de la carrera, para esto necesitamos simplificar las operaciones de lectura y escritura en diferentes hilos. Es importante comprender que un programa multiproceso, en contraste con uno secuencial, tiene varias ejecuciones. Y para decir que un programa tiene alguna propiedad, es necesario demostrar que el programa tiene esta propiedad no en una de las ejecuciones, sino en todas las ejecuciones.

Para demostrar que el programa no es de carreras, debe hacer esto para todas las actuaciones. Intentemos hacer SA y marquemos la variable a con un modificador vol谩til . Las variables vol谩tiles tendr谩n el prefijo v.

Presentamos una nueva hip贸tesis . Si la variable a se vuelve vol谩til , entonces ninguna ejecuci贸n del programa dar谩 el resultado (0, 0).

 w(va, 1) HB r(b): 0 鈥 w(b, 1) HB r(va): 0 

La ejecuci贸n no viola JMM . Leer va sucede bajo la carrera. Cualquier raza destruye la transitividad de HB.

Presentamos otra hip贸tesis . Si la variable b se vuelve vol谩til , ninguna ejecuci贸n del programa dar谩 el resultado (0, 0).

 w(a, 1) HB r(vb): 0 鈥 w(vb, 1) HB r(a): 0 

La ejecuci贸n no viola JMM. La lectura de ocurre bajo la carrera.

Probemos la hip贸tesis de que si las variables a y b son vol谩tiles , ninguna ejecuci贸n del programa dar谩 el resultado (0, 0).

Comprueba la primera actuaci贸n.

 w(va, 1) SO r(vb): 0 SO w(vb, 1) SO r(va): 0 

Dado que todas las acciones en el programa SA (espec铆ficamente leer o escribir una variable vol谩til ), obtenemos el orden SO completo entre todas las acciones. Esto significa que r (va) deber铆a ver w (va, 1). Esta ejecuci贸n viola JMM .

Es necesario proceder a la siguiente ejecuci贸n para confirmar la hip贸tesis. Pero dado que habr谩 SO para cualquier ejecuci贸n, puede desviarse del formalismo: es obvio que el resultado (0, 0) viola el JMM para cualquier ejecuci贸n.

Para usar el modelo de rotaci贸n, debe agregar vol谩til para las variables a y b. Tal programa dar谩 los resultados (1,1), (1,0) o (0,1).

Al final, podemos decir que los programas muy simples son bastante simples de analizar.

Pero los programas complejos con una gran cantidad de ejecuciones y datos compartidos son dif铆ciles de analizar, ya que debe verificar todas las ejecuciones.

Otros modelos de ejecuci贸n competitiva


驴Por qu茅 considerar otros modelos de programaci贸n competitivos?

El uso de hilos y primitivas de sincronizaci贸n puede resolver todos los problemas. Todo esto es cierto, pero el problema es que examinamos un ejemplo de una docena de l铆neas de c贸digo, donde 4 l铆neas de c贸digo hacen un trabajo 煤til.

Y all铆 encontramos un mont贸n de preguntas, hasta el punto de que sin la especificaci贸n ni siquiera podr铆amos calcular correctamente todos los resultados posibles. Los hilos y las primitivas de sincronizaci贸n son una cosa muy dif铆cil, cuyo uso ciertamente est谩 justificado en algunos casos. B谩sicamente, estos casos est谩n relacionados con el rendimiento.

Lo siento, me refiero mucho a Elizarov, pero 驴qu茅 puedo hacer si una persona realmente tiene experiencia en este campo? Entonces, tiene otro informe maravilloso , "Millones de citas por segundo en Java puro", en el que dice que un estado inmutable es bueno, pero no copiar茅 mis millones de citas en cada transmisi贸n, lo siento. Pero no todos tienen millones de citas, muchas tienen tareas m谩s modestas. 驴Existen modelos de programaci贸n competitivos que le permitan olvidarse de JMM y a煤n as铆 escribir c贸digo seguro y competitivo?

Si est谩 realmente interesado en esta pregunta, le recomiendo el libro de Paul Butcher, "Siete modelos de competencia en siete semanas". Revelamos los secretos de los flujos ". Desafortunadamente, no fue posible encontrar suficiente informaci贸n sobre el autor, pero el libro deber铆a abrir los ojos a nuevos paradigmas. Desafortunadamente, no tengo experiencia con muchos otros modelos de competencia, as铆 que obtuve la rese帽a de este libro.

Respondiendo la pregunta anterior. Seg煤n tengo entendido, existen modelos de programaci贸n competitivos que pueden al menos reducir en gran medida la necesidad de conocer los matices de JMM. Sin embargo, si hay un estado mutable y flujos, entonces no arruines ninguna abstracci贸n sobre ellos, todav铆a habr谩 un lugar donde estos flujos deber铆an sincronizar el acceso al estado. Otra pregunta es que probablemente no tenga que sincronizar el acceso usted mismo, por ejemplo, un marco puede responder a esto. Pero como hemos dicho, tarde o temprano, puede ocurrir abstracci贸n.

Puede excluir el estado mutable en absoluto. En el mundo de la programaci贸n funcional, esta es una pr谩ctica normal. Si no hay estructuras mutables, entonces probablemente no habr谩 problemas con la memoria compartida por definici贸n. Hay representantes de lenguajes funcionales en la JVM, como Clojure. Clojure es un lenguaje funcional h铆brido, porque todav铆a le permite cambiar las estructuras de datos, pero proporciona herramientas m谩s eficientes y seguras para esto.

Los lenguajes funcionales son una gran herramienta para trabajar con c贸digo competitivo. Personalmente, no lo uso, porque mi 谩rea de actividad es el desarrollo m贸vil, y all铆 simplemente no es convencional. Aunque se pueden adoptar ciertos enfoques.

Otra forma de trabajar con datos mutables es evitar el intercambio de datos. Los actores son un modelo de programaci贸n. Los actores simplifican la programaci贸n al no permitir el acceso simult谩neo a los datos. Esto se logra por el hecho de que una funci贸n que realiza trabajo en un momento puede funcionar en un solo hilo.

Sin embargo, un actor puede cambiar el estado interno. Dado que en el siguiente momento, el mismo actor puede ejecutarse en otro hilo, esto puede ser un problema. El problema se puede resolver de diferentes maneras, en lenguajes de programaci贸n como Erlang o Elixir, donde el modelo de actor es una parte integral del lenguaje, puede usar la recursi贸n para llamar a un actor con un nuevo estado.

En Java, las recursiones pueden ser demasiado caras. Sin embargo, en Java existen marcos para un trabajo conveniente con este modelo, probablemente el m谩s popular es Akka. Los desarrolladores de Akka se han ocupado de todo, puede ir a la secci贸n de documentaci贸n de Akka y el Modelo de memoria de Java y leer sobre dos casos en los que el acceso a un estado compartido puede ocurrir desde diferentes hilos. Pero lo m谩s importante, la documentaci贸n dice qu茅 eventos se relacionan con "sucedi贸 antes". Es decir Esto significa que podemos cambiar el estado del actor tanto como queramos, pero cuando recibamos el siguiente mensaje y posiblemente lo procesemos en otro hilo, tenemos la garant铆a de ver todos los cambios realizados en otro hilo.

驴Por qu茅 es tan popular el modelo de subprocesamiento?


Examinamos dos modelos de programaci贸n competitiva, de hecho, hay a煤n m谩s de ellos que hacen que la programaci贸n competitiva sea m谩s f谩cil y segura.

Pero, 驴por qu茅 los hilos y las cerraduras siguen siendo tan populares?

Creo que la raz贸n es la simplicidad del enfoque, por supuesto, por un lado, es f谩cil cometer muchos errores no obvios con las transmisiones, dispararse en el pie, etc. Pero, por otro lado , no hay nada complicado en los flujos, especialmente si no piensa en las consecuencias .

En un momento dado, el n煤cleo puede ejecutar una instrucci贸n (de hecho, hay paralelismo en el nivel de instrucci贸n, pero ahora no importa), pero gracias a la multitarea, incluso en m谩quinas de un solo n煤cleo, se pueden ejecutar varios programas simult谩neamente (por supuesto, pseudo simult谩neamente).

Para que la multitarea funcione, necesita competencia. Como ya hemos descubierto, la competencia es imposible sin varios flujos de gesti贸n.

驴Cu谩ntos hilos cree usted que un programa que se ejecuta en un procesador de tel茅fono m贸vil de cuatro n煤cleos debe ser lo m谩s r谩pido y receptivo posible?

Puede haber varias docenas. Ahora la pregunta es, 驴por qu茅 necesitamos tantos subprocesos para un programa que se ejecuta en hardware que le permite ejecutar solo 2-4 subprocesos a la vez?

Para intentar responder a esta pregunta, suponga que solo nuestro programa se ejecuta en el dispositivo y nada m谩s. 驴C贸mo gestionar铆amos los recursos que nos proporcionan?

Puede asignar un n煤cleo para la interfaz de usuario, el resto del n煤cleo para cualquier otra tarea.Si uno de los hilos est谩 bloqueado, por ejemplo, el hilo puede acceder al controlador de memoria y esperar una respuesta, entonces obtendremos un n煤cleo bloqueado.

驴Qu茅 tecnolog铆as hay para resolver el problema?

Hay hilos en Java, podemos crear muchos hilos, y luego otros hilos podr谩n realizar operaciones mientras algunos hilos est谩n bloqueados. Con una herramienta como hilos, podemos simplificar nuestras vidas.

El enfoque con subprocesos no es gratuito, la creaci贸n de subprocesos generalmente lleva tiempo (se decide por grupos de subprocesos), se les asigna memoria, el cambio entre subprocesos es una operaci贸n costosa. Pero es relativamente f谩cil de programar con ellos, por lo que esta es una tecnolog铆a masiva que se usa ampliamente en lenguajes generales, como Java.

Java generalmente ama las transmisiones, no es necesario crear para cada acci贸n una transmisi贸n, hay cosas de nivel superior, como Executors, que le permiten trabajar con grupos y escribir c贸digo m谩s escalable y flexible. Las transmisiones son realmente convenientes, puede realizar una solicitud de bloqueo a la red y escribir el procesamiento de resultados en la siguiente l铆nea. Incluso si esperamos unos segundos para obtener el resultado, a煤n podemos realizar otras tareas, ya que el sistema operativo se encargar谩 de la distribuci贸n del tiempo del procesador entre los subprocesos.

Las transmisiones son populares no solo en el desarrollo de back-end, en el desarrollo m贸vil se considera bastante normal crear docenas de transmisiones para que pueda bloquear una transmisi贸n durante un par de segundos, esperando que los datos se descarguen a trav茅s de la red o los datos del socket.

Los lenguajes como Erlang o Clojure siguen siendo nicho y, por lo tanto, los modelos de programaci贸n competitivos que utilizan no son tan populares. Sin embargo, los pron贸sticos para ellos son los m谩s optimistas.

Conclusiones


Si est谩 desarrollando en la plataforma JVM, debe aceptar las reglas del juego indicadas por la plataforma. La 煤nica forma de escribir c贸digo multiproceso normal. Es muy deseable comprender el contexto de todo lo que sucede, por lo que ser谩 m谩s f谩cil aceptar las reglas del juego. Es incluso mejor mirar a su alrededor y conocer otros paradigmas, aunque no puede llegar a ning煤n lado desde el submarino, pero puede descubrir nuevos enfoques y herramientas.

Materiales adicionales


Trat茅 de colocar en el texto del art铆culo enlaces a fuentes de las cuales obtuve informaci贸n.

En general, el material JMM es f谩cil de encontrar en Internet. Aqu铆 publicar茅 enlaces a algunos materiales adicionales que est谩n asociados con JMM y que no pueden llamar mi atenci贸n de inmediato.

Lectura

  • Blog de Alexey Shipilev : s茅 lo que es obvio, pero es un pecado sin mencionar
  • El blog de Cheremin Ruslan : no ha escrito activamente 煤ltimamente, debe buscar sus entradas antiguas en el blog, cr茅anme que vale la pena, hay una fuente
  • Habr Gleb Smirnov : hay excelentes art铆culos sobre subprocesos m煤ltiples y el modelo de memoria
  • El blog de Roman Elizarov est谩 abandonado, pero las excavaciones arqueol贸gicas deben llevarse a cabo. En general, Roman hizo mucho para educar a la gente en la teor铆a de la programaci贸n multiproceso, b煤squela en los medios.



Problemas de podcasts que me parecieron particularmente interesantes. No se trata de JMM, se trata del infierno, que est谩 sucediendo en la gl谩ndula. Pero despu茅s de escucharlos, quiero besar a los creadores de JMM, que nos han protegido de todo esto.


Video

Adem谩s de los discursos de las personas mencionadas anteriormente, preste atenci贸n al video acad茅mico.

Source: https://habr.com/ru/post/440590/


All Articles