¿Qué significa threadsafe?

123

Recientemente intenté acceder a un cuadro de texto desde un hilo (que no sea el hilo de la interfaz de usuario) y se produjo una excepción. Decía algo acerca de que "el código no es seguro para subprocesos", así que terminé escribiendo un delegado (la muestra de MSDN ayudó) y llamándolo en su lugar.

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

Actualización: ¿tendré problemas serios si verifico

Controls.CheckForIllegalCrossThread..blah =true
Vivek Bernard
fuente
55
Por lo general, "hilo seguro" significa lo que la persona que usa el término cree que significa, al menos para esa persona. Como tal, no es una construcción de lenguaje muy útil: debe ser mucho, mucho más específico al hablar sobre el comportamiento del código enhebrado.
77
¿Duplicar ?: stackoverflow.com/questions/261683/…
Dave O.
@dave Lo siento, intenté buscar, pero me di por vencido ... gracias de todos modos ..
Vivek Bernard
1
un código que no surgeRace-Condition
Muhammad Babar

Respuestas:

121

Eric Lippert tiene una buena publicación de blog titulada ¿Qué es esto que llamas "seguro para subprocesos"? sobre la definición de seguridad del hilo tal como se encuentra en Wikipedia.

3 cosas importantes extraídas de los enlaces:

"Un fragmento de 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 múltiples hilos para acceder a los mismos datos compartidos, ..."

"... y la necesidad de acceder a un dato compartido a través de un solo hilo en un momento dado".

Definitivamente vale la pena leer!

Gregory Pakosz
fuente
24
Evite las respuestas de solo enlace, ya que puede volverse malo en cualquier momento en el futuro.
akhil_mittal
enlace actualizado: docs.microsoft.com/en-nz/archive/blogs/ericlippert/…
Ryan Buddicom
106

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

Vincent Ramdhanie
fuente
11
Esto se llama sincronización. ¿Correcto?
JavaTechnical
3
Si. Obligar a los diversos subprocesos a esperar el acceso a un recurso compartido se puede lograr con la sincronización.
Vincent Ramdhanie
Según la respuesta aceptada de Gregory, él está diciendo "" Un código es seguro para subprocesos si funciona correctamente durante la ejecución simultánea por múltiples subprocesos ". mientras dices "Para que el hilo sea seguro, entonces debes obligar a la persona 1 a esperar"; ¿no está diciendo que simultáneo es aceptable mientras dices que no lo es? ¿Puedes explicarlo?
Miel
Es lo mismo. Solo estoy sugiriendo un mecanismo simple como ejemplo de lo que hace que el código sea seguro. independientemente del mecanismo utilizado, aunque varios subprocesos que ejecutan el mismo código no deberían interferir entre sí.
Vincent Ramdhanie
Entonces, ¿esto solo se aplica al código que utiliza variables globales y estáticas entonces? Utilizando su ejemplo de personas que editan documentos, supongo que no tiene sentido evitar que la persona 2 ejecute el código de escritura de documentos en otro documento.
Aaron Franke
18

Wikipedia tiene un artículo sobre seguridad de hilos.

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

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

Un hilo es una ruta de ejecución de un programa. Un programa de un solo subproceso solo tendrá un subproceso y, por lo tanto, este problema no surge. Prácticamente todos los programas GUI tienen múltiples rutas de ejecución y, por lo tanto, subprocesos: hay al menos dos, uno para procesar la visualización de la GUI y entregar la entrada del usuario, y al menos otro para realizar realmente las operaciones del programa.

Esto se hace para que la interfaz de usuario siga respondiendo mientras el programa funciona descargando cualquier proceso de larga ejecución a cualquier subproceso que no sea de interfaz de usuario. Estos subprocesos pueden crearse una vez y existir durante la vida útil del programa, o simplemente crearse cuando sea necesario y destruirse cuando hayan terminado.

Como estos subprocesos a menudo necesitarán realizar acciones comunes (E / S de disco, resultados de salida en la pantalla, etc.), estas partes del código deberán escribirse de tal manera que puedan manejar llamadas desde múltiples subprocesos, a menudo en al mismo tiempo. Esto implicará cosas como:

  • Trabajando en copias de datos
  • Agregar bloqueos alrededor del código crítico
ChrisF
fuente
8

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

Considere el siguiente método:

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

Ahora el hilo A y el hilo B desean 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 (aún 0) en su propia variable tmp. El subproceso B finaliza 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 era 0 para el hilo A). Y luego guarda este valor en myInt. myInt es de nuevo 1.

Entonces, en este caso, el método AddOne se llamó dos veces, pero debido a que el método no se implementó de manera segura para subprocesos, el valor de myInt no es 2, como se esperaba, sino 1 porque el segundo subproceso leyó la variable myInt antes de que finalizara el primer subproceso 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 método sea seguro para subprocesos, pero si hay mucho trabajo por hacer en un método, esto desperdicia mucho espacio. Otra técnica es 'marcar solo una pequeña parte de un método como sincronizado'creando un bloqueo 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 seguros para subprocesos sin bloqueo, lo que significa que están construidos de tal manera que múltiples subprocesos pueden atravesarlos 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.

Sujith PS
fuente
si el método AddOne fue llamado dos veces
Sujith PS
6

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 tiene solo $ 10. Realizó el saldo de transferencia a otra cuenta mediante la banca móvil y, mientras tanto, realizó compras en línea utilizando la misma cuenta bancaria. Si esta cuenta bancaria no es segura, entonces el banco le permite realizar dos transacciones al mismo tiempo y luego el banco se declarará en bancarrota.

Threadsafe significa que el estado de un objeto no cambia si simultáneamente varios hilos intentan acceder al objeto.

Yasir Shabbir Choudhary
fuente
5

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

Jacky
fuente
4

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

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

La garantía puede limitarse potencialmente 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, generalmente se supone que implica para todos los entornos que el código se puede compilar y ejecutar.

Los módulos no seguros para subprocesos pueden funcionar correctamente con un uso simultáneo y multiproceso, pero a menudo esto se debe más a la suerte y la coincidencia que al diseño cuidadoso. Incluso si algún módulo no se rompe por debajo, puede romperse cuando se mueve a otros entornos.

Los 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 desordenar las estructuras de datos de maneras impredecibles y hacer que aparezcan 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.

Chris Vest
fuente
3

Hilo de seguridad : un programa seguro de subprocesos protege sus datos de errores de consistencia de memoria. En un programa altamente multiproceso, un programa seguro para subprocesos no causa ningún efecto secundario con múltiples operaciones de lectura / escritura de múltiples subprocesos en los mismos objetos. Diferentes hilos pueden compartir y modificar datos de objetos sin errores de consistencia.

Puede lograr la seguridad del subproceso mediante el uso de API de concurrencia avanzada Esta página de documentación proporciona buenas construcciones de programación para lograr la seguridad del subproceso.

Los objetos de bloqueo admiten modismos de bloqueo que simplifican muchas aplicaciones concurrentes.

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

Las Colecciones concurrentes facilitan la gestión de grandes colecciones de datos y pueden reducir en gran medida la necesidad de sincronización.

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

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

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

Ravindra babu
fuente
1

Claramente está trabajando en un entorno WinForms. Los controles de WinForms exhiben 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 ordenar la llamada nuevamente en el hilo principal.

La práctica normal de WinForms es tener un solo hilo dedicado a todo su trabajo de interfaz de usuario.

David M
fuente
1

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

Por ejemplo, he visto código que formateó números de coma flotante en cadena, si dos de estos se ejecutan en hilos diferentes, 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
Aaron Robson
fuente
-2

Para comprender la seguridad del hilo, lea las secciones a continuación :

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

Como un ejemplo más sustancial de delegación, construyamos una versión del rastreador de vehículos que delegue a una clase segura para subprocesos. Almacenamos las ubicaciones en un mapa, así que empezamos con una implementación Mapa flujos seguros, ConcurrentHashMap. También almacenamos la ubicación utilizando una clase de Punto inmutable en lugar de MutablePoint, como 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;
    }

}

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

DelegatingVehicleTrackeren el Listado 4.7 no usa ninguna sincronización explícita; todo el acceso al estado es administrado por ConcurrentHashMap, y todas las claves y valores del Mapa son inmutables.

Listado 4.7. Delegación de seguridad de subprocesos a un ConcurrentHashMap.

  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 hubiéramos usado la MutablePointclase original en lugar de Point, estaríamos rompiendo la encapsulación al permitir getLocationspublicar una referencia al estado mutable que no sea seguro para subprocesos. Tenga en cuenta que hemos cambiado ligeramente el comportamiento de la clase de rastreador 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 no modificable pero "en vivo" de las ubicaciones del vehículo. Esto significa que si el hilo A llama getLocationsy el hilo B luego modifica la ubicación de algunos de los puntos, esos cambios se reflejan en el Mapa devuelto al hilo A.

4.3.2. Variables estatales independientes

También podemos delegar la 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 variables de estado múltiple.

VisualComponenten el Listado 4.9 es un componente gráfico que permite a los clientes registrar oyentes para eventos de mouse y pulsaciones de teclas. Mantiene una lista de oyentes registrados de cada tipo, de modo que cuando se produce un evento se puede invocar a los oyentes apropiados. Pero no existe una relación entre el conjunto de oyentes del mouse y los oyentes clave; los dos son independientes y, por VisualComponentlo tanto, pueden 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<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);
    }

}

VisualComponentutiliza a CopyOnWriteArrayListpara almacenar cada lista de oyentes; Esta es una implementación de Lista segura para subprocesos especialmente adecuada para administrar listas de oyentes (ver Sección 5.2.3). Cada lista es segura para subprocesos y, dado que no existen restricciones que acoplen el estado de uno con el estado de la otra, VisualComponentpuede delegar sus responsabilidades de seguridad de subprocesos en el subyacentemouseListenerskeyListeners objetos y .

4.3.3. Cuando falla la delegación

La mayoría de las clases compuestas no son tan simples como VisualComponent: tienen invariantes que relacionan las variables de estado de sus componentes. NumberRangeen el Listado 4.10 usa dosAtomicIntegers 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 de números 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());
    }

}

NumberRangeno es seguro para subprocesos ; no preserva la invariante que restringe los niveles inferiores y superiores. Los métodos setLowery setUpperintentan respetar esta invariante, pero lo hacen mal. Ambas setLowery setUpperson secuencias de comprobar y luego actuar, pero no utilizan el bloqueo suficiente para hacerlas atómicas. Si el rango de números se mantiene (0, 10), y un hilo llama setLower(5)mientras otro llama setUpper(4), con un tiempo desafortunado ambos pasarán las verificaciones en los setters y se aplicarán ambas modificaciones. El resultado es que el rango ahora contiene (5, 4), un estado no válido . Entonces, mientras que los AtomicIntegers subyacentes son seguros para subprocesos, la clase compuesta no lo es . Debido a que las variables de estado subyacentes no son independientes,lower yupperNumberRange no puede simplemente delegar la seguridad de subprocesos a sus variables de estado de seguridad de subprocesos.

NumberRangepodría hacerse a prueba de hilos mediante el uso de bloqueo para mantener sus invariantes, como la protección inferior y superior con un bloqueo común. También debe evitar publicar más y menos para evitar que los clientes subviertan sus invariantes.

Si una clase tiene acciones compuestas, como lo NumberRangehace, la delegación sola nuevamente no 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 se pueda delegar a las variables de estado subyacentes.

Si una clase se compone de múltiples variables de estado independientes seguras para subprocesos y no tiene operaciones que tengan transiciones de estado no válidas, puede delegar la seguridad de subprocesos a las variables de estado subyacentes.

3 revoluciones
fuente