¿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últiples hilos ejecutan ese segmento de código?
¿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últiples hilos ejecutan ese segmento de código?
Respuestas:
De Wikipedia:
La seguridad de subprocesos es un concepto de programación informática aplicable en el contexto de programas de subprocesos múltiples. Un código es seguro para subprocesos si funciona correctamente durante la ejecución simultánea de múltiples subprocesos. En particular, debe satisfacer la necesidad de que varios subprocesos accedan a los mismos datos compartidos, y la necesidad de que un solo subproceso acceda a un dato compartido en un momento dado.
Hay algunas formas de lograr la seguridad del hilo:
Re-fascinación:
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 guardar la 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 utilizando mecanismos que aseguran que solo un hilo lea o escriba los datos compartidos en cualquier momento. Se requiere un gran cuidado si un fragmento de código accede a múltiples datos compartidos; los problemas incluyen condiciones de carrera, puntos muertos, bloqueos en vivo, inanición y varios otros males enumerados en muchos libros de texto de sistemas operativos.
Almacenamiento local de subprocesos:
Las variables se localizan para 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 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. Como 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 bloqueo de hilos.
Lee mas:
http://en.wikipedia.org/wiki/Thread_safety
en francés: http://fr.wikipedia.org/wiki/Threadsafe (muy breve)
El código seguro para subprocesos es un código que funcionará incluso si muchos subprocesos lo ejecutan simultáneamente.
Una pregunta más informativa es qué hace que el código no sea seguro para 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 automático)
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
Me gusta la definición de la concurrencia de Java de Brian Goetz en la práctica por su amplitud
"Una clase es segura para subprocesos si se comporta correctamente cuando se accede desde múltiples subprocesos, independientemente de la programación o intercalación 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. "
Como otros han señalado, la seguridad del subproceso significa que un 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 codificación más compleja, por lo que no siempre es deseable. Si una clase se puede usar de forma segura en un solo subproceso, 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 a StringBuffer
puede ser utilizada por múltiples subprocesos 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 está construido 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 internas o las operaciones que deberían ejecutarse sin interrupciones están protegidas contra diferentes modificaciones 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 enhebrada tenga un comportamiento no deseado.
Acceso a la 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 asegurarse del comportamiento de su función. La regla general es mantener la cerradura por 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. es un punto muerto potencial donde la primera función esperará a que B se desbloquee cuando la segunda función esperará a que A se desbloquee. Este problema probablemente no ocurrirá en su entorno de desarrollo y solo de vez en cuando. Para evitarlo, todas las cerraduras deben estar siempre en el mismo orden.
Si y no.
La seguridad de subprocesos es un poco más que solo asegurarse de que solo un subproceso a la vez acceda a sus datos compartidos. Debe garantizar el acceso secuencial a los datos compartidos y, al mismo tiempo, evitar las condiciones de carrera , los puntos muertos , los bloqueos en vivo y la falta de recursos .
Los resultados impredecibles cuando se ejecutan varios subprocesos no es una condición necesaria del código seguro para subprocesos, pero a menudo es un subproducto. Por ejemplo, podría tener un esquema de productor-consumidor configurado con una cola compartida, un subproceso de productor y pocos subprocesos de consumidor, y el flujo de datos podría ser perfectamente predecible. Si comienza a presentar más consumidores, verá resultados más aleatorios.
En esencia, muchas cosas pueden salir mal en un entorno de subprocesos múltiples (reordenamiento de instrucciones, objetos parcialmente construidos, la misma variable que tiene diferentes valores en diferentes subprocesos debido al almacenamiento en caché a nivel de CPU, etc.).
Me gusta la definición dada por Java Concurrency in Practice :
Una [porción de código] es segura para subprocesos si se comporta correctamente cuando se accede desde múltiples subprocesos, independientemente de la programación o intercalación 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
Por correctamente , significan que el programa se comporta de conformidad con sus especificaciones.
Ejemplo contribuido
Imagine que implementa un contador. Se podría decir que se comporta correctamente si:
counter.next()
nunca devuelve un valor que ya se ha devuelto antes (suponemos que no hay desbordamiento, etc. por simplicidad)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).
Simplemente, el código funcionará bien si muchos hilos están ejecutando este código al mismo tiempo.
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 los problemas de depuración con código roscado, 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 de ejecución para su corrección, entonces necesita otros mecanismos de sincronización más allá de los necesarios para la seguridad del subproceso para garantizar esto.
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 multiproceso, un programa seguro para subprocesos no causa efectos secundarios a los datos compartidos .
Eche un vistazo a esta pregunta SE para obtener más detalles:
El programa seguro para subprocesos garantiza la coherencia de la memoria .
Desde la página de documentación de Oracle en API concurrente avanzada:
Propiedades de consistencia de memoria:
El Capítulo 17 de la Especificación del lenguaje Java ™ define la relación que ocurre antes de las operaciones de memoria, tales como lecturas y escrituras de variables compartidas. Se garantiza que los resultados de una escritura de un hilo sean visibles para una lectura de otro hilo solo si la operación de escritura ocurre antes de la operación de lectura .
Las construcciones synchronized
y volatile
, así como los métodos Thread.start()
y Thread.join()
, pueden formar relaciones que suceden antes .
Los métodos de todas las clases java.util.concurrent
y sus subpaquetes amplían estas garantías a la sincronización de nivel superior. En particular:
Runnable
a Executor
suceder, antes de que comience su ejecución. Del mismo modo para Callables sometidos a un ExecutorService
.Future
acciones anteriores a la recuperación posterior del resultado a través Future.get()
de otro hilo.Lock.unlock, Semaphore.release, and CountDownLatch.countDown
acciones de suceder antes de un método exitoso de "adquisición", como Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
en el mismo objeto sincronizador en otro subproceso.Exchanger
, las acciones previas a exchange()
cada subproceso suceden, antes de las posteriores al intercambio correspondiente () en otro subproceso.CyclicBarrier.await
y Phaser.awaitAdvance
(así como sus variantes) suceden antes de las acciones realizadas por la acción de barrera, y las acciones realizadas por la acción de barrera suceden antes de las acciones posteriores a un retorno exitoso de la espera correspondiente en otros hilos.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:
Esto significa que las variables definidas DENTRO de su método son siempre seguras. 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.
No se garantiza que la programación de subprocesos sea redonda . Una tarea puede acaparar totalmente la CPU a expensas de hilos de la misma prioridad. Puedes usar Thread.yield () para tener conciencia. Puede usar (en java) Thread.setPriority (Thread.NORM_PRIORITY-1) para reducir la prioridad de un hilo
Además ten cuidado con:
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 fundamentalmente no lo es.
Tenga en cuenta que la imprevisibilidad de los resultados es una consecuencia de las "condiciones de carrera" que probablemente provoquen que los datos se modifiquen en un orden diferente al esperado.
Respondamos esto con el ejemplo:
class NonThreadSafe {
private int counter = 0;
public boolean countTo10() {
count = count + 1;
return (count == 10);
}
El countTo10
método agrega uno al contador y luego devuelve verdadero si el recuento ha alcanzado 10. Solo debe devolver verdadero una vez.
Esto funcionará siempre que solo un hilo ejecute el código. Si dos hilos ejecutan el código al mismo tiempo, pueden ocurrir varios problemas.
Por ejemplo, si el recuento comienza como 9, un subproceso podría agregar 1 al recuento (haciendo 10) pero luego un segundo subproceso podría ingresar al método y agregar nuevamente 1 (haciendo 11) antes de que el primer subproceso tenga la oportunidad de ejecutar la comparación con 10 Luego, ambos hilos hacen la comparación y descubren que el conteo es 11 y ninguno devuelve verdadero.
Entonces este código no es seguro para subprocesos.
En esencia, todos los problemas de subprocesos múltiples son causados por alguna variación de este tipo de problema.
La solución es garantizar que la suma y la comparación no puedan separarse (por ejemplo, rodeando las dos declaraciones con algún tipo de código de sincronización) o ideando una solución que no requiera dos operaciones. Tal código sería seguro para subprocesos.
Al menos en C ++, pienso en thread-safe como un nombre poco apropiado ya que deja mucho fuera del nombre. Para ser seguro para subprocesos, el código generalmente debe ser proactivo al respecto. Generalmente no es una cualidad pasiva.
Para que una clase sea segura, tiene que tener características "adicionales" que agreguen sobrecarga. Estas características son parte de la implementación de la clase y, en general, ocultas de la interfaz. Es decir, diferentes subprocesos pueden acceder a cualquiera de los miembros de la clase sin tener que preocuparse de entrar en conflicto con un acceso concurrente por un subproceso diferente Y pueden hacerlo de una manera muy perezosa, usando el estilo de codificación humana normal simple y antiguo, sin tener que hacerlo todas esas cosas locas de sincronización que ya están incluidas en las entrañas del código que se llama.
Y es por eso que algunas personas prefieren usar el término sincronizado internamente .
Hay tres conjuntos principales de terminología para estas ideas que he encontrado. El primero e históricamente más popular (pero peor) es:
El segundo (y mejor) es:
Un tercero es:
hilo seguro ~ a prueba de hilo ~ sincronizado internamente
Un ejemplo de un internamente sincronizado (aka. Flujos seguros o de prueba del hilo del sistema) es un restaurante donde una gran cantidad te recibe en la puerta, y Deshabilita cola de sí mismo. El anfitrión es parte del mecanismo del restaurante para tratar con múltiples clientes, y puede usar algunos trucos bastante complicados para optimizar el asiento de los clientes que esperan, como tener en cuenta el tamaño de su grupo o cuánto tiempo parecen tener. , o incluso hacer reservas por teléfono. El restaurante está sincronizado internamente porque todo esto es parte de la interfaz para interactuar con él.
no es seguro para subprocesos (pero agradable) ~ compatible con subprocesos ~ sincronizado externamente ~ sin subprocesos
Supongamos que vas al banco. Hay una línea, es decir, contención para los cajeros bancarios. Como no eres un salvaje, reconoces que lo mejor que puedes hacer en medio de una disputa por un recurso es hacer cola como un ser civilizado. Nadie técnicamente te obliga a hacer esto. Esperamos que tenga la programación social necesaria para hacerlo por su cuenta. En este sentido, el lobby del banco está sincronizado externamente. ¿Deberíamos decir que no es seguro? eso es lo que la implicación es que si vas con el hilo de seguridad , hilo insegura conjunto terminología bipolar. No es un muy buen conjunto de términos. La mejor terminología está sincronizada externamente,El lobby del banco no es hostil para que varios clientes accedan a él, pero tampoco hace el trabajo de sincronizarlos. Los clientes lo hacen ellos mismos.
Esto también se llama "rosca libre", donde "libre" es como "libre de piojos", o en este caso, cerraduras. Bueno, más exactamente, primitivas de sincronización. Eso no significa que el código pueda ejecutarse en múltiples hilos sin esas primitivas. Simplemente significa que no viene con ellos ya instalados y depende de usted, el usuario del código, instalarlos usted mismo como mejor le parezca. La instalación de sus propias primitivas de sincronización puede ser difícil y requiere pensar mucho en el código, pero también puede conducir al programa más rápido posible al permitirle personalizar cómo se ejecuta el programa en las CPU hipertrontadas de hoy.
no es seguro para subprocesos (y está mal) ~ subproceso hostil ~ no sincronizable
Un ejemplo de la analogía cotidiana de un sistema hostil es un tirón con un auto deportivo que se niega a usar sus luces intermitentes y cambiar de carril de manera involuntaria. Su estilo de conducción es hostil o no psicronizable porque no tiene forma de coordinarse con ellos, y esto puede conducir a una disputa por el mismo carril, sin resolución, y por lo tanto un accidente ya que dos autos intentan ocupar el mismo espacio, sin ningún protocolo para Prevenir esto. Este patrón también se puede considerar más ampliamente como antisocial, lo que prefiero porque es menos específico para los hilos y, por lo tanto, más generalmente aplicable a muchas áreas de programación.
El primer y más antiguo conjunto de terminología no logra hacer una distinción más fina entre la hostilidad del hilo y la compatibilidad del hilo . La compatibilidad de subprocesos es más pasiva que la denominada seguridad de subprocesos, pero eso no significa que el código llamado no sea seguro para el uso concurrente de subprocesos. Simplemente significa que es pasivo acerca de la sincronización que permitiría esto, posponiéndolo en el código de llamada, en lugar de proporcionarlo como parte de su implementación interna. La compatibilidad de subprocesos es la forma en que el código probablemente debería escribirse de manera predeterminada en la mayoría de los casos, pero lamentablemente a menudo también se lo considera erróneamente como inseguro, como si fuera intrínsecamente anti seguridad, lo que es un gran punto de confusión para los programadores.
NOTA: Muchos manuales de software en realidad usan el término "seguro para subprocesos" para referirse a "compatible con subprocesos", lo que agrega aún más confusión a lo que ya era un desastre. Evito el término "seguro para subprocesos" e "inseguro para subprocesos" a toda costa por esta misma razón, ya que algunas fuentes llamarán a algo "seguro para subprocesos" mientras que otros lo llamarán "inseguro para subprocesos" porque no pueden estar de acuerdo sobre si tiene que cumplir con algunos estándares adicionales de seguridad (primitivas de sincronización), o simplemente NO ser hostil para ser considerado "seguro". Así que evite esos términos y use los términos más inteligentes en su lugar, para evitar comunicaciones erróneas peligrosas con otros ingenieros.
Esencialmente, nuestro objetivo es subvertir el caos.
Hacemos eso creando sistemas deterministas en los que podemos confiar. El determinismo es costoso, principalmente debido a los costos de oportunidad de perder paralelismo, canalización y reordenamiento. Tratamos de minimizar la cantidad de determinismo que necesitamos para mantener nuestros costos bajos, al tiempo que evitamos tomar decisiones que erosionen aún más el poco determinismo que podemos permitirnos.
La sincronización de hilos se trata de aumentar el orden y disminuir el caos. Los niveles en los que hace esto corresponden a los términos mencionados anteriormente. El nivel más alto significa que un sistema se comporta de una manera completamente predecible cada vez. El segundo nivel significa que el sistema se comporta lo suficientemente bien como para que el código de llamada pueda detectar de manera confiable la imprevisibilidad. Por ejemplo, una activación espuria en una variable de condición o una falla al bloquear un mutex porque no está listo. El tercer nivel significa que el sistema no se comporta lo suficientemente bien como para jugar con nadie más y solo se puede ejecutar con un solo hilo sin incurrir en caos.
En lugar de pensar que el código o las clases son seguros para los hilos o no, creo que es más útil pensar que las acciones son seguras para los hilos. Dos acciones son seguras para subprocesos si se comportan como se especifica cuando se ejecutan desde contextos de subprocesos arbitrarios. En muchos casos, las clases admitirán algunas combinaciones de acciones de manera segura para subprocesos y otras no.
Por ejemplo, muchas colecciones, como las listas de matrices y los conjuntos de hash, garantizarán que si inicialmente se accede a ellos exclusivamente con un hilo, y nunca se modifican después de que una referencia sea visible para cualquier otro hilo, cualquier combinación puede leerlos de manera arbitraria. de hilos sin interferencia.
Más interesante aún, algunas colecciones de hash-sets, como la original no genérica en .NET, pueden ofrecer una garantía de que siempre que no se elimine ningún elemento, y siempre que solo un hilo los escriba, cualquier hilo que intente leer la colección se comportará como si tuviera acceso a una colección donde las actualizaciones podrían retrasarse y ocurrir en un orden arbitrario, pero que de lo contrario se comportarían normalmente. Si el hilo # 1 agrega X y luego Y, y el hilo # 2 busca y ve Y y luego X, sería posible que el hilo # 2 vea que Y existe pero X no; si dicho comportamiento es "seguro para subprocesos" dependerá de si el subproceso # 2 está preparado para enfrentar esa posibilidad.
Como nota final, algunas clases, especialmente el bloqueo de bibliotecas de comunicaciones, pueden tener un método "cerrar" o "Eliminar" que es seguro para subprocesos con respecto a todos los demás métodos, pero ningún otro método que sea seguro para subprocesos con respecto a El uno al otro. Si un hilo realiza una solicitud de lectura de bloqueo y un usuario del programa hace clic en "cancelar", no habría forma de que el hilo que intenta realizar la lectura no emitiera una solicitud de cierre. Sin embargo, la solicitud de cierre / eliminación puede establecer asincrónicamente un indicador que hará que la solicitud de lectura se cancele lo antes posible. Una vez que se realiza el cierre en cualquier subproceso, el objeto se volvería inútil y todos los intentos de acciones futuras fallarían de inmediato,
En palabras más 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 son mencionadas por otros respondedores como 1. El resultado debería ser el mismo si ejecuta un hilo o varios hilos sobre él, etc.