programming languages concurrent multithreading language-agnostic programming-languages concurrency

multithreading - languages - ¿Qué se entiende por código "seguro para subprocesos"?



concurrent programming languages (15)

¿Significa que dos hilos no pueden cambiar los datos subyacentes simultáneamente? ¿O significa que el segmento de código dado se ejecutará con resultados predecibles cuando más de un hilo lo esté ejecutando?


Como han señalado otros, la seguridad de subprocesos significa que un fragmento de código funcionará sin errores si es utilizado por más de un subproceso a la vez.

Vale la pena tener en cuenta que esto a veces tiene un costo, tiempo de computadora y una codificación más compleja, por lo que no siempre es deseable. Si una clase se puede usar de manera segura en un solo hilo, puede ser mejor hacerlo.

Por ejemplo, Java tiene dos clases que son casi equivalentes, StringBuffer y StringBuilder . La diferencia es que StringBuffer es seguro para subprocesos, por lo que múltiples subprocesos pueden usar una sola instancia de un StringBuffer a la vez. StringBuilder no es seguro para subprocesos, y está diseñado como un reemplazo de mayor rendimiento para esos casos (la gran mayoría) cuando la Cadena está formada por un solo subproceso.



El código seguro para subprocesos funciona según lo especificado, incluso cuando se ingresan simultáneamente por diferentes subprocesos. Esto a menudo significa que las estructuras de datos internos u operaciones que deberían ejecutarse sin interrupciones están protegidas contra diferentes modificaciones al mismo tiempo.


En esencia, muchas cosas pueden salir mal en un entorno de múltiples subprocesos (reordenación de instrucciones, objetos construidos parcialmente, la misma variable tiene diferentes valores en diferentes subprocesos debido al almacenamiento en caché en el nivel de CPU, etc.).

Me gusta la definición dada por Java Concurrency in Practice :

Una [parte del código] es segura para subprocesos si se comporta correctamente cuando se accede desde múltiples subprocesos, independientemente de la programación o el intercalado de la ejecución de dichos subprocesos por el entorno de ejecución, y sin sincronización adicional u otra coordinación por parte del Código de llamada.

Por correctamente significan que el programa se comporta de acuerdo con sus especificaciones.

Ejemplo elaborado

Imagina que implementas un contador. Se podría decir que se comporta correctamente si:

  • counter.next() nunca devuelve un valor que ya haya sido devuelto anteriormente (asumimos que no hay desbordamiento, etc. para simplificar)
  • todos los valores de 0 al valor actual se han devuelto en alguna etapa (no se omite ningún valor)

Un contador de seguridad de subprocesos se comportaría de acuerdo con esas reglas, independientemente de cuántos subprocesos accedan a él simultáneamente (lo que normalmente no sería el caso de una implementación ingenua).

Nota: cross-post en programadores


En palabras más simples: P Si es seguro ejecutar varios subprocesos en un bloque de código, es seguro para subprocesos *

*las condiciones se aplican

Las condiciones se mencionan en otras respuestas como 1. El resultado debería ser el mismo si ejecuta un hilo o varios hilos sobre él, etc.


Me gusta la definición de la Concurrencia de Java en la Práctica de Brian Goetz por su exhaustividad.

"Una clase es segura para subprocesos si se comporta correctamente cuando se accede desde múltiples subprocesos, independientemente de la programación o el intercalado de la ejecución de dichos subprocesos en el entorno de ejecución, y sin sincronización adicional u otra coordinación por parte del código de llamada. "


Me gustaría agregar más información sobre otras buenas respuestas.

La seguridad de subprocesos implica que varios subprocesos pueden escribir / leer datos en el mismo objeto sin errores de inconsistencia de memoria. En un programa altamente multihilo, un programa seguro para subprocesos no causa efectos secundarios a los datos compartidos .

Echa un vistazo a esta pregunta de SE para más detalles:

¿Qué significa threadsafe?

El programa Thread Safe garantiza la consistencia de la memoria .

Desde la page documentación de page en API concurrente avanzada:

Propiedades de consistencia de memoria:

El Capítulo 17 de la Especificación del lenguaje Java ™ define la relación de suceso anterior en las operaciones de memoria, como las lecturas y escrituras de variables compartidas. Se garantiza que los resultados de una escritura por un subproceso serán visibles para una lectura por otro subproceso solo si la operación de escritura ocurre antes de la operación de lectura .

Las construcciones synchronized y volatile , así como los Thread.start() y Thread.join() , pueden formar relaciones de Thread.join() antes .

Los métodos de todas las clases en java.util.concurrent y sus subpaquetes extienden estas garantías a la sincronización de nivel superior. En particular:

  1. Las acciones en un subproceso antes de colocar un objeto en cualquier colección concurrente ocurren antes de las acciones posteriores al acceso o eliminación de ese elemento de la colección en otro subproceso.
  2. Acciones en un hilo antes del envío de un Runnable a un Executor antes de que comience su ejecución. De manera similar para los Callables enviados a un ExecutorService .
  3. Las acciones tomadas por el cálculo asíncrono representado por un Future suceden antes de las acciones posteriores a la recuperación del resultado a través de Future.get() en otro hilo.
  4. Acciones antes de "liberar" métodos de sincronización como Lock.unlock, Semaphore.release, and CountDownLatch.countDown suceden antes de un método de "adquisición" exitosa como Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await en el mismo objeto sincronizador en otro hilo.
  5. Para cada par de hilos que intercambian objetos con éxito a través de un Exchanger , las acciones antes del exchange() en cada hilo suceden, antes de las posteriores al intercambio () correspondiente en otro hilo.
  6. Las acciones antes de llamar a CyclicBarrier.await y Phaser.awaitAdvance (así como sus variantes) suceden antes de que las acciones realizadas por la acción de barrera y las acciones realizadas por la acción de barrera ocurran antes de un retorno exitoso de la espera correspondiente en otras trapos.

No confunda la seguridad del hilo con el determinismo. El código seguro para subprocesos también puede ser no determinista. Dada la dificultad de depurar problemas con el código de subprocesos, este es probablemente el caso normal. :-)

La seguridad de subprocesos simplemente garantiza que cuando un subproceso está modificando o leyendo datos compartidos, ningún otro subproceso puede acceder a él de una manera que cambie los datos. Si su código depende de un cierto orden para que la ejecución sea correcta, entonces necesita otros mecanismos de sincronización más allá de los requeridos para la seguridad de subprocesos para garantizar esto.


Para completar otras respuestas:

La sincronización es solo una preocupación cuando el código en su método hace una de dos cosas:

  1. funciona con algún recurso externo que no es seguro para subprocesos.
  2. Lee o cambia un objeto persistente o campo de clase

Esto significa que las variables definidas DENTRO de su método siempre son seguras para subprocesos. Cada llamada a un método tiene su propia versión de estas variables. Si el método es llamado por otro hilo, o por el mismo hilo, o incluso si el método se llama a sí mismo (recursión), los valores de estas variables no se comparten.

La programación de hilos no está garantizada como round-robin . Una tarea puede saturar totalmente la CPU a expensas de subprocesos de la misma prioridad. Puedes usar Thread.yield () para tener conciencia. Puedes usar (en java) Thread.setPriority (Thread.NORM_PRIORITY-1) para disminuir la prioridad de un hilo

Además ten cuidado con:

  • el gran costo de tiempo de ejecución (ya mencionado por otros) en aplicaciones que se repiten en estas estructuras "seguras para subprocesos".
  • Se supone que Thread.sleep (5000) duerme durante 5 segundos. Sin embargo, si alguien cambia la hora del sistema, puede dormir por un tiempo muy largo o sin tiempo. El sistema operativo registra el tiempo de activación en forma absoluta, no relativa.

Si y no.

La seguridad de los subprocesos es un poco más que asegurarse de que solo se acceda a sus datos compartidos a la vez. livelocks garantizar el acceso secuencial a los datos compartidos y, al mismo tiempo, evitar las condiciones de la carrera , los deadlocks , los deadlocks livelocks y la livelocks recursos .

Los resultados impredecibles cuando se ejecutan varios subprocesos no son una condición necesaria para el código seguro de subprocesos, pero a menudo es un subproducto. Por ejemplo, podría tener un esquema producer-consumer configurado con una cola compartida, un hilo productor y pocos hilos del consumidor, y el flujo de datos podría ser perfectamente predecible. Si empiezas a introducir más consumidores, verás resultados más aleatorios.


Si y si. Implica que los datos no son modificados por más de un hilo simultáneamente. Sin embargo, su programa podría funcionar como se esperaba y parecer seguro para subprocesos, incluso si es fundamentalmente no.

Tenga en cuenta que la imprevisibilidad de los resultados es una consecuencia de las "condiciones de carrera" que probablemente hacen que los datos se modifiquen en un orden distinto al esperado.


Simplemente, el código se ejecutará bien si muchos subprocesos ejecutan este código al mismo tiempo.


Una forma más fácil de entenderlo es lo que hace que el código no sea seguro para subprocesos. Hay dos problemas principales que harán que una aplicación de subprocesos tenga un comportamiento no deseado.

  • Acceso a variable compartida sin bloqueo
    Esta variable podría ser modificada por otro hilo mientras se ejecuta la función. Desea evitarlo con un mecanismo de bloqueo para estar seguro del comportamiento de su función. La regla general es mantener la cerradura durante el menor tiempo posible.

  • Punto muerto causado por la dependencia mutua en la variable compartida
    Si tiene dos variables compartidas A y B. En una función, primero bloquea A y luego bloquea B. En otra función, comienza a bloquear B y después de un tiempo, bloquea A. Este es un punto muerto potencial donde la primera función espere a que B se desbloquee cuando la segunda función esperará a que A se desbloquee. Este problema probablemente no ocurra en su entorno de desarrollo y solo de vez en cuando. Para evitarlo, todos los bloqueos deben estar siempre en el mismo orden.


Una pregunta más informativa es qué es lo que hace que el código no sea seguro, y la respuesta es que hay cuatro condiciones que deben cumplirse ... Imagine el siguiente código (y su traducción en lenguaje de máquina)

totalRequests = totalRequests + 1 MOV EAX, [totalRequests] // load memory for tot Requests into register INC EAX // update register MOV [totalRequests], EAX // store updated value back to memory

  1. La primera condición es que hay ubicaciones de memoria que son accesibles desde más de un hilo. Por lo general, estas ubicaciones son variables globales / estáticas o son accesibles a partir de variables globales / estáticas. Cada subproceso obtiene su propio marco de pila para las variables locales con ámbito de función / método, por lo que estas variables de función / método locales, otoh, (que están en la pila) son accesibles solo desde el único hilo que posee esa pila.
  2. La segunda condición es que hay una propiedad (a menudo llamada invariante ), que está asociada con estas ubicaciones de memoria compartida, que debe ser verdadera o válida, para que el programa funcione correctamente. En el ejemplo anterior, la propiedad es que " totalRequests debe representar con precisión el número total de veces que un subproceso ha ejecutado alguna parte de la declaración de incremento ". Normalmente, esta propiedad invariable debe mantenerse verdadera (en este caso, totalRequests debe tener un recuento preciso) antes de que se produzca una actualización para que la actualización sea correcta.
  3. La tercera condición es que la propiedad invariable NO se mantiene durante alguna parte de la actualización real. (Es transitoriamente inválido o falso durante alguna parte del procesamiento). En este caso particular, desde el momento en que se solicita totalRequests hasta el momento en que se almacena el valor actualizado, totalRequests no satisface el invariante.
  4. La cuarta y última condición que debe ocurrir para que ocurra una carrera (y para que el código NO sea ​​"seguro para subprocesos") es que otro subproceso debe poder acceder a la memoria compartida mientras el invariante se rompe, lo que provoca una incoherencia o comportamiento incorrecto

de wikipedia:

La seguridad de subprocesos es un concepto de programación de computadoras aplicable en el contexto de programas de subprocesos múltiples. Un fragmento de código es seguro para subprocesos si funciona correctamente durante la ejecución simultánea de varios subprocesos. En particular, debe satisfacer la necesidad de que múltiples subprocesos accedan a los mismos datos compartidos, y la necesidad de que solo un subproceso pueda acceder a un dato compartido en un momento dado.

...

Hay algunas maneras de lograr la seguridad del hilo:

Re-entrada

Escribir código de tal manera que pueda ser ejecutado parcialmente por una tarea, reingresado por otra tarea, y luego reanudado desde la tarea original. Esto requiere el almacenamiento de información de estado en variables locales para cada tarea, generalmente en su pila, en lugar de en variables estáticas o globales.

Exclusión mutua

El acceso a los datos compartidos se serializa mediante mecanismos que aseguran que solo un hilo lea o escriba los datos compartidos en cualquier momento. Se requiere mucho cuidado si una pieza de código accede a múltiples piezas de datos compartidas; los problemas incluyen condiciones de carrera, puntos muertos, bloqueos de vida, hambruna y varios otros males enumerados en muchos libros de texto de sistemas operativos.

Almacenamiento de hilo local

Las variables se localizan para que cada hilo tenga su propia copia privada. Estas variables conservan sus valores a través de subrutinas y otros límites de código, y son seguras para subprocesos ya que son locales para cada subproceso, incluso aunque el código que accede a ellas pueda ser reentrante.

Operaciones atómicas

Se accede a los datos compartidos utilizando operaciones atómicas que no pueden ser interrumpidas por otros subprocesos. Esto generalmente requiere el uso de instrucciones especiales en lenguaje de máquina, que pueden estar disponibles en una biblioteca de tiempo de ejecución. Dado que las operaciones son atómicas, los datos compartidos siempre se mantienen en un estado válido, sin importar a qué otros hilos acceden. Las operaciones atómicas forman la base de muchos mecanismos de bloqueo de hilos.

Lee mas :

http://en.wikipedia.org/wiki/Thread_safety