2010-01-09 29 views
80

Recientemente traté de acceder a un cuadro de texto desde 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.¿Qué significa threadsafe?

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

Actualización: Voy a correr en cualquier problema serio si puedo comprobar

Controls.CheckForIllegalCrossThread..blah =true 
+0

por "eso" me refiero a una excepción –

+4

Por lo general, "hilo de seguridad" significa cualquiera que sea la persona que utiliza el término piensa que significa, al menos a esa persona. Como tal, no es una construcción de lenguaje muy útil; necesitas ser mucho, mucho más específico cuando hablas del comportamiento del código enhebrado. –

+5

¿Duplicado ?: http: // stackoverflow.com/questions/261683/what-is-meant-by-thread-safe-code –

Respuesta

83

Eric Lippert tiene una buena entrada de blog titulada What is this thing you call "thread safe"? sobre la definición de seguridad de hilo como se encuentra en Wikipedia.

cosas

3 importantes extraídos de los enlaces:

“una pieza de código es thread-safe si funciona correctamente durante ejecución simultánea de múltiples hilos.” ​​

“En particular, debe satisfacer la necesidad de múltiples hilos a tener acceso a los mismos datos compartidos, ... "

" ... y la necesidad de acceder a una pieza de datos compartida por un solo hilo en cualquier momento dado mi."

Definitivamente vale la pena leer!

+10

Por favor, evite las respuestas de solo enlace, ya que puede ser malo en cualquier momento en el futuro. –

11

Wikipedia tiene un artículo sobre Seguridad para subprocesos.

Este definitions page (hay que saltar un anuncio - lo siento) define así:

En la programación de computadoras, flujos seguros describe una parte del programa o rutina que se puede llamar desde varios subprocesos de programación y sin interacción no deseada entre los hilos.

Un subproceso es una ruta de ejecución de un programa. Un único programa de subprocesos solo tendrá un subproceso y, por lo tanto, este problema no se produce. Prácticamente todos los programas 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 programa. Esto es para que la IU siga respondiendo mientras el programa está funcionando.

1

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.

73

En términos simples, threadsafe significa que es seguro acceder desde múltiples hilos. Cuando usa múltiples hilos en un programa 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 hacerlo seguro luego, debe forzar a la persona 1 a esperar que la persona 2 complete su tarea antes de permitir que la persona 1 edite el documento.

+6

Esto se llama sincronización. ¿Derecha? – user12458

+1

Sí. Obligar a varios subprocesos a esperar el acceso a un recurso compartido se puede lograr con la sincronización. –

+0

De la respuesta aceptada de Gregory, dice "Un código es seguro para subprocesos si funciona correctamente durante la ejecución simultánea por varios subprocesos." Mientras dices "Para hacerlo seguro, entonces debes obligar a la persona 1 a espera "; ¿no está diciendo que lo simultáneo es aceptable mientras dices que no lo es? ¿Puedes explicarme? – Honey

3

Un módulo es seguro para la ejecución de subprocesos si se garantiza que puede mantener sus invariantes frente a múltiples subprocesos y el uso concurrente.

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 arquitectura de CPU específica, pero debe ser válida 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 compilar y ejecutar el código.

Módulos de rosca pueden funcionan correctamente bajo el uso de rosca múltiple y concurrente, 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.

Errores de subprocesos múltiples 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.

1

Me parece 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 coma flotante en la 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 
3

Simplemente, hilo de seguridad significa que un método o una clase de instancia puede ser utilizado por múltiples hilos al mismo tiempo sin ningún tipo de problemas que ocurren.

Considérese el siguiente método:

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

Ahora enhebrar una y el hilo B tanto 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.

Así que en este caso el método AddOne se llamó 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 lea la variable myInt antes de que el primer hilo termine de actualizarlo.

Crear métodos de seguridad de 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 'marque 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 bloqueo, 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.

+0

¿Cómo se espera 2? –

+0

si el método AddOne se llamó dos veces –

4

Usted puede obtener una explicación más detallada del libro "Java concurrencia en la práctica":

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

1

Seguridad de los hilos: Un programa de seguro para hilos protege de datos de errores de coherencia de memoria. En un programa altamente multiproceso, un programa 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 la seguridad de subprocesos mediante el uso de API de simultaneidad avanzada. Esta documentación page proporciona buenas construcciones de programación para lograr la seguridad del hilo.

Lock Objects idiomas de bloqueo de soporte que simplifican muchas aplicaciones concurrentes.

Executors 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.

Concurrent Collections hacen que sea más fácil administrar grandes colecciones de datos, y pueden reducir en gran medida la necesidad de sincronización.

Atomic Variables tienen características que minimizan la sincronización y ayudan a evitar errores de consistencia de memoria.

ThreadLocalRandom (en JDK 7) proporciona la generación eficiente de números pseudoaleatorios desde varios subprocesos.

Consulte los paquetes java.util.concurrent y java.util.concurrent.atomic para otras construcciones de programación.

1

En ejemplo del mundo real para el lego es

dejar suponer que tener una cuenta bancaria con el internet y la banca móvil y su cuenta de tener sólo 10 $. Ha realizado la transferencia de saldo 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 subprocesos intentan acceder al objeto.

0

Para entender hilo de seguridad, lea a continuación sections:

4.3.1. Ejemplo: Rastreador de vehículos usando la 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 utilizando una clase de punto inmutable en lugar de MutablePoint, que se muestra en el listado 4.6.

Lista 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 está gestionado por ConcurrentHashMap, y todas las claves y valores del Mapa son inmutables.

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

public class DelegatingVehicleTracker { 

    private final ConcurrentMap<String, Point> locations; 
    private final Map<String, Point> unmodifiableMap; 

    public DelegatingVehicleTracker(Map<String, Point> points) { 
     this.locations = new ConcurrentHashMap<String, Point>(points); 
     this.unmodifiableMap = Collections.unmodifiableMap(locations); 
    } 

    public Map<String, Point> 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 se hubiera utilizado la clase original MutablePoint en lugar de punto, que sería romper la encapsulación dejando getLocations publicar 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 devuelto 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 involucren las múltiples variables de estado.

VisualComponent en el listado 4.9 es un componente gráfico que permite a los clientes registrar escuchas 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; los dos son independientes y, por lo tanto, VisualComponent puede delegar sus obligaciones de seguridad de subprocesos a dos listas subyacentes seguras para subprocesos.

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

public class VisualComponent { 
    private final List<KeyListener> keyListeners 
             = new CopyOnWriteArrayList<KeyListener>(); 
    private final List<MouseListener> mouseListeners 
             = new CopyOnWriteArrayList<MouseListener>(); 

    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 un 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 en los objetos subyacentes 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 usa dos AtomicIntegers para administrar su estado, pero impone una restricción adicional: que el primer número sea menor o igual que el segundo.

Lista 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 es no 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 de manera deficiente. Tanto setLower como setUpper son secuencias de verificación y actuación, pero no utilizan el bloqueo suficiente para que sean atómicas. Si el rango de números se mantiene (0, 10), y un hilo llama a setLower(5), mientras que otro hilo llama al setUpper(4), con un poco de mala sincronización ambos pasarán las verificaciones en los incubadores y se aplicarán ambas modificaciones. El resultado es que el rango ahora tiene (5, 4) - un estado inválido. Así que mientras que los AtomicIntegers subyacentes son seguros para subprocesos, la clase compuesta no es. Debido a que las variables de estado subyacentes lower y upper no son independientes, NumberRange no puede simplemente delegar seguridad de subprocesos a sus variables de estado de subprocesos.

NumberRange podría hacerse a prueba de roscas mediante el bloqueo para mantener sus invariantes, como la protección de la parte inferior y superior con un bloqueo 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, 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 bloqueo 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 se compone de múltiples variables de estado independientes de subprocesos y no tiene operaciones que tengan transiciones de estado no válidas, puede delegar seguridad de subprocesos a las variables de estado subyacentes.