¿Qué significa threadsafe?

Recientemente traté de acceder a un cuadro de texto de un hilo (que no sea el hilo de UI) y se lanzó una excepción. Dijo algo acerca de que el “código no era seguro para subprocesos” y terminé escribiendo un delegado (me ayudó una muestra de MSDN) y llamé en su lugar.

Pero aun así no entendía por qué todo el código extra era necesario.

Actualización: ¿Me encontraré con algún problema serio si reviso

Controls.CheckForIllegalCrossThread..blah =true 

Eric Lippert tiene una buena entrada de blog titulada ¿Qué es esto que llamas “hilo seguro”? sobre la definición de seguridad de hilo como se encuentra en Wikipedia.

3 cosas importantes extraídas de los enlaces:

“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 de datos compartida por un solo hilo en un momento dado”.

¡Definitivamente vale la pena leerlo!

En términos simples, threadsafe significa que es seguro acceder desde múltiples hilos. Cuando usa múltiples hilos en un progtwig y cada uno intenta acceder a una estructura de datos o ubicación común en la memoria, pueden ocurrir varias cosas malas. Entonces, agrega un código adicional para evitar esas cosas malas. Por ejemplo, si dos personas estaban escribiendo el mismo documento al mismo tiempo, la segunda persona para guardar sobrescribirá el trabajo de la primera persona. Para que el hilo sea seguro, debe obligar a la persona 2 a esperar que la persona 1 complete su tarea antes de permitir que la persona 2 edite el documento.

Wikipedia tiene un artículo sobre seguridad de subprocesos.

Esta página de definiciones (debe omitir un anuncio, lo siento) lo define así:

En la progtwigción de computadoras, thread-safe describe una porción de progtwig o rutina que puede invocarse desde múltiples hilos de progtwigción sin interacción no deseada entre los hilos.

Un hilo es una ruta de ejecución de un progtwig. Un único progtwig de subprocesos solo tendrá un subproceso y, por lo tanto, este problema no se produce. Prácticamente todos los progtwigs de GUI tienen una ruta de ejecución múltiple y, por lo tanto, subprocesos, uno para procesar la visualización de la GUI y la entrega de información del usuario, otros para realizar realmente las operaciones del progtwig. Esto es para que la IU siga respondiendo mientras el progtwig está funcionando.

Un módulo es seguro para subprocesos si garantiza que puede mantener sus invariantes ante el uso de múltiples subprocesos y concurrencia.

Aquí, un módulo puede ser una estructura de datos, clase, objeto, método / procedimiento o función. Básicamente una pieza de código y datos relacionados.

La garantía puede estar limitada a ciertos entornos, como una architecture de CPU específica, pero debe mantenerse para esos entornos. Si no hay una delimitación explícita de los entornos, se suele interpretar que se aplica a todos los entornos donde se puede comstackr y ejecutar el código.

Los módulos inseguros con hilos pueden funcionar correctamente en usos múltiples y concurrentes, pero esto se debe más a la suerte y la coincidencia que a un diseño cuidadoso. Incluso si algún módulo no funciona por usted, puede romperse cuando se lo mueva a otros entornos.

Los errores de varios subprocesos a menudo son difíciles de depurar. Algunos de ellos solo ocurren ocasionalmente, mientras que otros se manifiestan agresivamente; esto también puede ser específico del entorno. Pueden manifestarse como resultados sutilmente incorrectos o puntos muertos. Pueden estropear las estructuras de datos de formas impredecibles y provocar la aparición de otros errores aparentemente imposibles en otras partes remotas del código. Puede ser muy específico de la aplicación, por lo que es difícil dar una descripción general.

Simplemente, el hilo seguro significa que un método o instancia de clase puede ser utilizado por múltiples hilos al mismo tiempo sin que ocurra ningún problema.

Considera el siguiente método:

 private int myInt = 0; public int AddOne() { int tmp = myInt; tmp = tmp + 1; myInt = tmp; return tmp; } 

Ahora los hilos A y B le gustaría ejecutar AddOne (). pero A comienza primero y lee el valor de myInt (0) en tmp. Ahora, por alguna razón, el planificador decide detener el hilo A y diferir la ejecución al hilo B. El hilo B ahora también lee el valor de myInt (todavía 0) en su propia variable tmp. El hilo B termina todo el método, por lo que al final myInt = 1. Y se devuelve 1. Ahora es el turno del hilo A nuevamente. El hilo A continúa. Y agrega 1 a tmp (tmp fue 0 para el hilo A). Y luego guarda este valor en myInt. myInt es otra vez 1.

Entonces, en este caso, el método AddOne fue llamado dos veces, pero como el método no se implementó de manera segura, el valor de myInt no es 2, como se esperaba, sino 1 porque el segundo hilo leyó la variable myInt antes de que terminara el primer hilo actualizándolo

Crear métodos seguros para subprocesos es muy difícil en casos no triviales. Y hay bastantes técnicas. En Java puede marcar un método como sincronizado, esto significa que solo un hilo puede ejecutar ese método en un momento dado. Los otros hilos esperan en línea. Esto hace que un hilo de método sea seguro, pero si hay mucho trabajo por hacer en un método, entonces esto desperdicia mucho espacio. Otra técnica es ‘marcar solo una pequeña parte de un método como sincronizado’ creando un candado o semáforo, y bloqueando esta pequeña parte (generalmente llamada la sección crítica). Incluso hay algunos métodos que se implementan como seguro de subprocesos sin locking, lo que significa que están diseñados de tal manera que varios subprocesos pueden correr a través de ellos al mismo tiempo sin causar problemas, este puede ser el caso cuando solo un método ejecuta una llamada atómica. Las llamadas atómicas son llamadas que no se pueden interrumpir y solo se pueden hacer por un hilo a la vez.

Puede obtener más explicaciones del libro “Concurrencia de Java en la práctica”:

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.

Seguridad de subprocesos : un progtwig seguro de subprocesos protege sus datos contra errores de coherencia de memoria. En un progtwig altamente multiproceso, un progtwig seguro para hilos no causa ningún efecto secundario con múltiples operaciones de lectura / escritura de múltiples hilos en los mismos objetos. Los diferentes subprocesos pueden compartir y modificar datos de objeto sin errores de consistencia.

Puede lograr seguridad de subprocesos mediante el uso de API de simultaneidad avanzada. Esta página de documentación proporciona buenas construcciones de progtwigción para lograr la seguridad del hilo.

Los objetos de locking admiten expresiones de locking que simplifican muchas aplicaciones concurrentes.

Los ejecutores definen una API de alto nivel para iniciar y administrar subprocesos. Las implementaciones de Executor proporcionadas por java.util.concurrent proporcionan administración de grupo de subprocesos adecuada para aplicaciones a gran escala.

Las Colecciones concurrentes facilitan la administración de grandes colecciones de datos y pueden reducir enormemente la necesidad de sincronización.

Las variables atómicas tienen características que minimizan la sincronización y ayudan a evitar errores de coherencia de memoria.

ThreadLocalRandom (en JDK 7) proporciona una generación eficiente de números pseudoaleatorios de múltiples hilos.

Consulte también los paquetes java.util.concurrent y java.util.concurrent.atomic para otras construcciones de progtwigción.

En el mundo real, el ejemplo para el profano es

Supongamos que tiene una cuenta bancaria con Internet y banca móvil y su cuenta solo tiene 10 $. Realizó el saldo de Transferencia a otra cuenta mediante la banca móvil y, mientras tanto, realizó compras en línea con la misma cuenta bancaria. Si esta cuenta bancaria no es “THREAD SAFE”, entonces el banco le permite realizar dos transacciones iguales y luego el banco se convertirá en bancarrota.

ThreadSafe significa que el estado del objeto no cambia si simultáneamente varios hilos intentan acceder al objeto.

Está trabajando claramente en un entorno WinForms. Los controles de WinForms muestran afinidad de subprocesos, lo que significa que el subproceso en el que se crean es el único subproceso que se puede utilizar para acceder y actualizarlos. Es por eso que encontrará ejemplos en MSDN y en otros lugares que demuestran cómo devolver la llamada al hilo principal.

La práctica normal de WinForms es tener un hilo único dedicado a todo su trabajo de IU.

Encuentro que el concepto de http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 es lo que generalmente considero como subprocesamiento inseguro, que es cuando un método tiene y depende de un efecto secundario como una variable global.

Por ejemplo, he visto un código que formateó números de punto flotante en una cadena, si dos de ellos se ejecutan en diferentes hilos, el valor global de decimalSeparator se puede cambiar permanentemente a ‘.’

 //built in global set to locale specific value (here a comma) decimalSeparator = ',' function FormatDot(value : real): //save the current decimal character temp = decimalSeparator //set the global value to be decimalSeparator = '.' //format() uses decimalSeparator behind the scenes result = format(value) //Put the original value back decimalSeparator = temp 

Para comprender la seguridad del hilo, lea las siguientes secciones :

4.3.1. Ejemplo: Rastreador de vehículos usando delegación

Como un ejemplo más sustancial de delegación, construyamos una versión del rastreador de vehículos que delegue en una clase segura para subprocesos. Almacenamos las ubicaciones en un Mapa, por lo que comenzamos con una implementación de Mapa sin hilos, ConcurrentHashMap . También almacenamos la ubicación usando una clase de Punto inmutable en lugar de MutablePoint , que se muestra en el Listado 4.6.

Listado 4.6. Clase de punto inmutable utilizada por DelegatingVehicleTracker.

  class Point{ public final int x, y; public Point() { this.x=0; this.y=0; } public Point(int x, int y) { this.x = x; this.y = y; } } 

Point es seguro para subprocesos porque es inmutable. Los valores inmutables se pueden compartir y publicar libremente, por lo que ya no es necesario copiar las ubicaciones al devolverlos.

DelegatingVehicleTracker en el listado 4.7 no utiliza ninguna sincronización explícita; todo el acceso al estado lo administra ConcurrentHashMap , y todas las claves y valores del Mapa son inmutables.

Listado 4.7. Delegación de seguridad de subprocesos a una coincidenciaHashMap.

  public class DelegatingVehicleTracker { private final ConcurrentMap locations; private final Map unmodifiableMap; public DelegatingVehicleTracker(Map points) { this.locations = new ConcurrentHashMap(points); this.unmodifiableMap = Collections.unmodifiableMap(locations); } public Map getLocations(){ return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable } public Point getLocation(String id) { return locations.get(id); } public void setLocation(String id, int x, int y) { if(locations.replace(id, new Point(x, y)) == null) { throw new IllegalArgumentException("invalid vehicle name: " + id); } } 

}

Si hubiéramos utilizado la clase MutablePoint original en lugar de Point, estaríamos rompiendo la encapsulación al permitir que getLocations publique una referencia al estado mutable que no es seguro para subprocesos. Tenga en cuenta que hemos cambiado ligeramente el comportamiento de la clase de seguimiento de vehículos; mientras que la versión del monitor devolvió una instantánea de las ubicaciones, la versión de delegación devuelve una vista inmodificable pero “en vivo” de las ubicaciones del vehículo. Esto significa que si el hilo A llama a getLocations y el hilo B modifica posteriormente la ubicación de algunos de los puntos, esos cambios se reflejan en el Mapa que se devuelve al hilo A.

4.3.2. Variables de estado independientes

También podemos delegar seguridad de subprocesos a más de una variable de estado subyacente siempre que esas variables de estado subyacentes sean independientes, lo que significa que la clase compuesta no impone invariantes que impliquen múltiples variables de estado.

VisualComponent en el Listado 4.9 es un componente gráfico que permite a los clientes registrar oyentes para eventos de mouse y pulsación de teclas. Mantiene una lista de oyentes registrados de cada tipo, de modo que cuando ocurre un evento, se pueden invocar los oyentes apropiados. Pero no existe una relación entre el conjunto de oyentes de ratón y oyentes clave; ambos son independientes y, por VisualComponent tanto, VisualComponent puede delegar sus obligaciones de seguridad de subprocesos en dos listas subyacentes seguras para subprocesos.

Listado 4.9. Delegación de seguridad de subprocesos a múltiples variables de estado subyacentes.

 public class VisualComponent { private final List keyListeners = new CopyOnWriteArrayList(); private final List mouseListeners = new CopyOnWriteArrayList(); public void addKeyListener(KeyListener listener) { keyListeners.add(listener); } public void addMouseListener(MouseListener listener) { mouseListeners.add(listener); } public void removeKeyListener(KeyListener listener) { keyListeners.remove(listener); } public void removeMouseListener(MouseListener listener) { mouseListeners.remove(listener); } } 

VisualComponent utiliza una CopyOnWriteArrayList para almacenar cada lista de oyentes; esta es una implementación de Lista segura para subprocesos particularmente adecuada para administrar listas de escucha (ver Sección 5.2.3). Cada lista es segura para subprocesos y, como no existen restricciones que acoplen el estado de uno con el estado del otro, VisualComponent puede delegar sus responsabilidades de seguridad de subprocesos a los mouseListeners subyacentes de mouseListeners y keyListeners .

4.3.3. Cuando la delegación falla

La mayoría de las clases compuestas no son tan simples como VisualComponent : tienen invariantes que relacionan las variables de estado de sus componentes. NumberRange en el Listado 4.10 utiliza dos AtomicIntegers para administrar su estado, pero impone una restricción adicional: que el primer número sea menor o igual que el segundo.

Listado 4.10. Clase de rango numérico que no protege suficientemente sus invariantes. No hagas esto

 public class NumberRange { // INVARIANT: lower < = upper private final AtomicInteger lower = new AtomicInteger(0); private final AtomicInteger upper = new AtomicInteger(0); public void setLower(int i) { //Warning - unsafe check-then-act if(i > upper.get()) { throw new IllegalArgumentException( "Can't set lower to " + i + " > upper "); } lower.set(i); } public void setUpper(int i) { //Warning - unsafe check-then-act if(i < lower.get()) { throw new IllegalArgumentException( "Can't set upper to " + i + " < lower "); } upper.set(i); } public boolean isInRange(int i){ return (i >= lower.get() && i < = upper.get()); } } 

NumberRange no es seguro para subprocesos ; no conserva el invariante que restringe inferior y superior. Los métodos setLower y setUpper intentan respetar este invariante, pero lo hacen mal. Tanto setLower como setUpper son setUpper verificación y acto, pero no usan suficiente locking para hacerlas atómicas. Si el rango de números se mantiene (0, 10), y un hilo llama a setLower(5) mientras otro hilo llama a setUpper(4) , con un poco de mala suerte ambos pasarán las verificaciones en los setters y se aplicarán ambas modificaciones. El resultado es que el rango ahora se mantiene (5, 4) - un estado inválido . Entonces, aunque los AtomicIntegers subyacentes son seguros para subprocesos, la clase compuesta no lo es . Debido a que las variables de estado subyacentes lower y upper no son independientes, NumberRange no puede simplemente delegar seguridad de hilo a sus variables de estado seguro para subprocesos.

NumberRange se puede hacer seguro para subprocesos mediante el locking para mantener sus invariantes, como la guarda inferior y superior con un locking común. También debe evitar publicar más bajo y más alto para evitar que los clientes subviertan sus invariantes.

Si una clase tiene acciones compuestas, como NumberRange hace NumberRange , la delegación por sí sola tampoco es un enfoque adecuado para la seguridad de subprocesos. En estos casos, la clase debe proporcionar su propio locking para garantizar que las acciones compuestas sean atómicas, a menos que toda la acción compuesta también pueda delegarse a las variables de estado subyacentes.

Si una clase está compuesta de múltiples variables independientes de estado de subprocesos y no tiene operaciones que tengan transiciones de estado no válidas, entonces puede delegar seguridad de subprocesos a las variables de estado subyacentes.