¿Qué se entiende por código “thread-safe”?

¿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?

de wikipedia:

La seguridad del subproceso es un concepto de progtwigción informática aplicable en el contexto de progtwigs de subprocesos múltiples. Un trozo de código es seguro para subprocesos si funciona correctamente durante la ejecución simultánea por varios subprocesos. En particular, debe satisfacer la necesidad de múltiples hilos para acceder a los mismos datos compartidos, y la necesidad de acceder a una pieza compartida de datos por un solo hilo en un momento dado.

Hay algunas maneras de lograr la seguridad del hilo:

Reentrada

Escribir el código de forma tal que pueda ser parcialmente ejecutado 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 stack, en lugar de en variables estáticas o globales.

Exclusión mutua

El acceso a los datos compartidos se serializa utilizando mecanismos que aseguran que solo un hilo lea o escriba los datos compartidos en cualquier momento. Se requiere gran cuidado si un fragmento de código accede a múltiples piezas compartidas de datos: problemas que incluyen condiciones de carrera, interlockings, lockings, inanición y varios otros males enumerados en muchos libros de texto de sistemas operativos.

Almacenamiento local de subprocesos

Las variables se localizan de modo que cada hilo tenga su propia copia privada. Estas variables conservan sus valores a través de la subrutina y otros límites de código, y son seguros para subprocesos, ya que son locales para cada subproceso, aunque el código que accede a ellos puede ser reentrante.

Operaciones atómicas

Se accede a los datos compartidos mediante el uso de operaciones atómicas que no pueden ser interrumpidas por otros hilos. Esto generalmente requiere el uso de instrucciones especiales de 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 accedan. Las operaciones atómicas forman la base de muchos mecanismos de locking de hilos.

Lee mas :

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


El código Thread-safe es un código que funcionará incluso si muchos Threads lo están ejecutando simultáneamente.

http://mindprod.com/jgloss/threadsafe.html

Una pregunta más informativa es lo que hace que el código no sea seguro para los subprocesos, y la respuesta es que hay cuatro condiciones que deben ser ciertas … Imagine el siguiente código (y es la traducción del lenguaje 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 accesibles desde más de un hilo. Típicamente, estas ubicaciones son variables globales / estáticas o la memoria de stack puede alcanzarse desde variables globales / estáticas. Cada subproceso obtiene su propio marco de stack para variables locales de función / método, por lo que estas variables locales de función / método, otoh, (que están en la stack) solo son accesibles desde el único subproceso que posee esa stack.
  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 progtwig funcione correctamente. En el ejemplo anterior, la propiedad es que ” totalRequests debe representar con precisión el número total de veces que un hilo ha ejecutado alguna parte de la instrucción de incremento “. Por lo general, esta propiedad invariante debe ser cierta (en este caso, TotalRequests debe contener 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 invariante NO se mantiene durante una parte de la actualización real. (Es transitoriamente inválido o falso durante alguna parte del proceso). En este caso particular, desde el momento en que se obtiene la TotalRequests hasta el momento en que se almacena el valor actualizado, totalRequests no satisface la invariante.
  4. La cuarta y última condición que debe ocurrir para que una carrera suceda (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 está roto, lo que causa inconsistencia o comportamiento incorrecto

Me gusta la definición de Java Concurrency in Practice de Brian Goetz por su exhaustividad

“Una clase es segura para subprocesos si se comporta correctamente cuando se accede desde varios subprocesos, independientemente de la progtwigción o el entrelazado de la ejecución de esos subprocesos por el entorno de tiempo de ejecución, y sin sincronización adicional u otra coordinación por parte del código de llamada. ”

Thread-safe-code funciona según lo especificado, incluso cuando se ingresa simultáneamente por diferentes hilos. Esto a menudo significa que las estructuras de datos internas u operaciones que deben ejecutarse ininterrumpidamente están protegidas contra diferentes modificaciones al mismo tiempo.

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

Vale la pena ser consciente de que esto a veces tiene un costo, de tiempo de la computadora y una encoding más compleja, por lo que no siempre es deseable. Si una clase se puede usar con seguridad solo en un 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 una sola instancia de un StringBuffer puede ser utilizada por múltiples hilos 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 el String se genera con un solo subproceso.

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

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

  • Punto muerto causado por la dependencia mutua de la variable compartida
    Si tiene dos variables compartidas A y B. En una función, bloquea A primero 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 espere a que se desbloquee A. Este problema probablemente no ocurra en su entorno de desarrollo y solo de vez en cuando. Para evitarlo, todos los lockings deben estar siempre en el mismo orden.

Si y no.

La seguridad del subproceso es un poco más que solo asegurarse de que se accede a sus datos compartidos por solo un hilo a la vez. Debe garantizar el acceso secuencial a los datos compartidos, al tiempo que evita las condiciones de carrera , los interlockings , los lockings automáticos y la inanición de recursos .

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

Básicamente, muchas cosas pueden salir mal en un entorno de múltiples hilos (reordenamiento de instrucciones, objetos parcialmente construidos, misma variable con diferentes valores en diferentes hilos debido a la caché en el nivel de la CPU, etc.).

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

Una [porción del código] es segura para subprocesos si se comporta correctamente cuando se accede desde varios subprocesos, independientemente de la progtwigción o el entrelazado de la ejecución de esos subprocesos por el entorno de tiempo de ejecución, y sin sincronización adicional u otra coordinación por parte del código de llamada

De forma correcta , significan que el progtwig se comporta de acuerdo con sus especificaciones.

Ejemplo de ejemplo

Imagine que implementa un contador. Se podría decir que se comporta correctamente si:

  • counter.next() nunca devuelve un valor que ya se haya devuelto antes (suponemos que no hay desbordamiento, etc. para simplificar)
  • todos los valores desde 0 hasta el valor actual se han devuelto en algún momento (no se omite ningún valor)

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

Nota: publicación cruzada en progtwigdores

Simplemente: el código funcionará bien si muchos hilos están ejecutando este código al mismo tiempo.

No confundas 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 del hilo, este es probablemente el caso normal. 🙂

La seguridad de subprocesos simplemente garantiza que cuando un subproceso modifica o lee 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 de ejecución para la corrección, entonces necesita otros mecanismos de sincronización más allá de los necesarios para la seguridad de la rosca 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 estas 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 son siempre seguras en cuanto a los hilos. 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 (recursividad), los valores de estas variables no se comparten.

No se garantiza que la progtwigción de subprocesos sea round-robin . Una tarea puede obstruir totalmente la CPU a expensas de los hilos de la misma prioridad. Puedes usar Thread.yield () para tener una 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 iteran sobre estas estructuras “seguras para hilos”.
  • 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 SO registra el tiempo de despertar en forma absoluta, no relativa.

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

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

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

¿Qué significa threadsafe?

El progtwig Thread safe garantiza la consistencia de la memoria .

Desde la página de documentación de Oracle en la API concurrente avanzada:

Propiedades de consistencia de memoria:

El Capítulo 17 de La especificación del lenguaje Java ™ define la relación de pasar antes a las operaciones de memoria, como lecturas y escrituras de variables compartidas. Se garantiza que los resultados de un subproceso de escritura por uno sean visibles para una lectura de 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 pase previo .

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

  1. Las acciones en un subproceso antes de colocar un objeto en cualquier colección simultánea ocurren antes que las acciones posteriores al acceso o eliminación de ese elemento de la colección en otro subproceso.
  2. Las acciones en un hilo antes de la presentación de un Runnable a un Executor suceden, antes de que comience su ejecución. Del mismo modo para Callables presentado a un ExecutorService .
  3. Las acciones tomadas por el cálculo asincrónico 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. Las acciones previas a la “liberación” de métodos de sincronización como Lock.unlock, Semaphore.release, and CountDownLatch.countDown ocurren antes que las acciones posteriores a un método de “adquisición” exitoso 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 previas al exchange() en cada hilo ocurren -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 sucedan antes de acciones posteriores a un retorno exitoso desde la espera correspondiente en otra trapos.

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

Tenga en cuenta que la imprevisibilidad de los resultados es una consecuencia de las “condiciones de carrera” que probablemente den lugar a una modificación de los datos en un orden diferente al esperado.

En palabras simples: P Si es seguro ejecutar múltiples hilos en un bloque de código, es seguro para hilos *

*las condiciones se aplican

Las condiciones se mencionan por otros answeres como 1. El resultado debe ser el mismo si ejecuta un hilo o múltiples hilos sobre él, etc.