¿Qué hace que un método sea seguro para subprocesos? ¿Cuales son las normas?

156

¿Existen reglas / pautas generales para lo que hace que un método sea seguro para subprocesos? Entiendo que probablemente haya un millón de situaciones puntuales, pero ¿qué pasa en general? ¿Es así de simple?

  1. Si un método solo accede a variables locales, es seguro para subprocesos.

¿Es asi? ¿Se aplica eso también a los métodos estáticos?

Una respuesta, proporcionada por @Cybis, fue:

Las variables locales no se pueden compartir entre subprocesos porque cada subproceso tiene su propia pila.

¿Es ese el caso de los métodos estáticos también?

Si se pasa un método a un objeto de referencia, ¿eso rompe la seguridad del hilo? He investigado un poco, y hay mucho sobre ciertos casos, pero esperaba poder definir, usando solo unas pocas reglas, pautas a seguir para asegurarme de que un método sea seguro para subprocesos.

Entonces, supongo que mi última pregunta es: "¿Existe una breve lista de reglas que definen un método seguro para subprocesos? Si es así, ¿cuáles son?"

EDITAR
Aquí se han hecho muchos puntos buenos. Creo que la verdadera respuesta a esta pregunta es: "No hay reglas simples para garantizar la seguridad de los hilos". Frio. Multa. Pero, en general , creo que la respuesta aceptada proporciona un buen resumen breve. Siempre hay excepciones. Que así sea. Puedo vivir con ello.

Bob Horn
fuente
59
No accederá a variables que también sean accesibles por otros hilos sin un bloqueo.
Hans Passant
44
¡Hanth Pathant se ha convertido en un Igor!
Martin James
3
Además ... "No accederá a variables que también sean accesibles por otros hilos sin bloqueo", a menos que eso importe mucho si el valor leído no es el último o es realmente incorrecto.
Martin James
Aquí hay un buen blog de Eric para llevarte a un torbellino.
RBT

Respuestas:

140

Si un método (instancia o estático) solo hace referencia a variables dentro de ese método, entonces es seguro para subprocesos porque cada subproceso tiene su propia pila:

En este caso, varios subprocesos podrían llamar ThreadSafeMethodsimultáneamente sin problemas.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

Esto también es cierto si el método llama a otro método de clase que solo hace referencia a variables de ámbito local:

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

Si un método accede a propiedades o campos (estado del objeto) (instancia o estático), entonces debe usar bloqueos para asegurarse de que los valores no sean modificados por un hilo diferente.

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

Debe tener en cuenta que cualquier parámetro pasado al método que no sea una estructura o sea inmutable podría ser mutado por otro hilo fuera del alcance del método.

Para garantizar la concurrencia adecuada, debe usar el bloqueo.

Para obtener más información, consulte la referencia de C # de la declaración de bloqueo y ReadWriterLockSlim .

lock es principalmente útil para proporcionar una funcionalidad a la vez,
ReadWriterLockSlimes útil si necesita múltiples lectores y escritores individuales.

Trevor Pilley
fuente
15
En el tercer ejemplo private string someValue;no es staticasí, cada instancia obtendrá una copia separada de esa variable. Entonces, ¿puede explicar cómo esto no es seguro para subprocesos?
Bharadwaj
29
@Bharadwaj si hay una instancia de la Thingclase a la que acceden varios subprocesos
Trevor Pilley el
112

Si un método solo accede a variables locales, es seguro para subprocesos. ¿Es asi?

Absolutamente no. Puede escribir un programa con solo una variable local a la que se acceda desde un solo subproceso que, sin embargo, no sea seguro para subprocesos:

https://stackoverflow.com/a/8883117/88656

¿Se aplica eso también a los métodos estáticos?

Absolutamente no.

Una respuesta, proporcionada por @Cybis, fue: "Las variables locales no se pueden compartir entre los hilos porque cada hilo tiene su propia pila".

Absolutamente no. La característica distintiva de una variable local es que solo es visible desde el ámbito local , no que está asignada en el grupo temporal . Es perfectamente legal y posible acceder a la misma variable local desde dos hilos diferentes. Puede hacerlo utilizando métodos anónimos, lambdas, bloques iteradores o métodos asíncronos.

¿Es ese el caso de los métodos estáticos también?

Absolutamente no.

Si se pasa un método a un objeto de referencia, ¿eso rompe la seguridad del hilo?

Tal vez.

He investigado un poco, y hay mucho sobre ciertos casos, pero esperaba poder definir, usando solo unas pocas reglas, pautas a seguir para asegurarme de que un método sea seguro para subprocesos.

Tendrás que aprender a vivir con desilusión. Este es un tema muy difícil.

Entonces, supongo que mi última pregunta es: "¿Existe una breve lista de reglas que definen un método seguro para subprocesos?

No Como viste en mi ejemplo anterior, un método vacío puede no ser seguro para subprocesos . También podría preguntar "¿hay una breve lista de reglas que garantice que un método sea correcto "? No no hay. La seguridad de los hilos no es más que un tipo de corrección extremadamente complicado.

Además, el hecho de que esté haciendo la pregunta indica su malentendido fundamental sobre la seguridad del hilo. La seguridad de subprocesos es una propiedad global , no local de un programa. La razón por la cual es tan difícil hacerlo bien es porque debe tener un conocimiento completo del comportamiento de enhebrado de todo el programa para garantizar su seguridad.

Nuevamente, mira mi ejemplo: cada método es trivial . Es la forma en que los métodos interactúan entre sí a un nivel "global" lo que hace que el programa se bloquee. No puede ver cada método y marcarlo como "seguro" y luego esperar que todo el programa sea seguro, como tampoco puede concluir que debido a que su casa está hecha de ladrillos 100% no huecos, la casa también es segura. no hueco El vacío de una casa es una propiedad global de todo, no un agregado de las propiedades de sus partes.

Eric Lippert
fuente
15
Declaración central: la seguridad de subprocesos es una propiedad global, no local de un programa.
Greg D
55
@BobHorn: class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } llame a M (), luego llame a C.getter y C.setter en dos hilos diferentes. La variable local ahora se puede escribir y leer en dos subprocesos diferentes aunque sea local. Nuevamente: la característica definitoria de una variable local es que es local , no que está en la pila del hilo .
Eric Lippert
3
@BobHorn: Creo que tiene más que ver con la comprensión de nuestras herramientas a un nivel tal que podamos hablar de ellas con autoridad y conocimiento. Por ejemplo, la pregunta original revelaba una falta de comprensión sobre qué es una variable local. Este error es bastante común, pero el hecho es que es un error y debe corregirse. La definición de una variable local es bastante precisa y debe tratarse en consecuencia. :) Esta no es una respuesta para "personas" que no entienden que existen casos patológicos. Estas son una aclaración para que pueda entender lo que realmente preguntó. :)
Greg D
77
@EricLippert: la documentación de MSDN para muchas clases declara que los miembros 'Public static (Shared en Visual Basic) de este tipo son seguros para subprocesos. No se garantiza que los miembros de la instancia sean seguros para subprocesos '. o a veces 'Este tipo es seguro para subprocesos'. (por ejemplo, String), ¿cómo se pueden hacer estas garantías cuando la seguridad de los hilos es una preocupación global?
Mike Zboray
3
@mikez: Buena pregunta. O esos métodos no mutan ningún estado, o cuando lo hacen, mutan el estado de manera segura sin bloqueos o, si usan bloqueos, lo hacen de una manera que garantiza que no se viole el "orden de bloqueo" global. Las cadenas son estructuras de datos inmutables cuyos métodos no mutan nada, por lo que las cadenas se pueden proteger fácilmente.
Eric Lippert
12

No hay una regla dura y rápida.

Aquí hay algunas reglas para hacer que el hilo de código sea seguro en .NET y por qué estas no son buenas reglas:

  1. La función y todas las funciones que llama deben ser puras (sin efectos secundarios) y utilizar variables locales. Aunque esto hará que su código sea seguro para subprocesos, también hay muy pocas cosas interesantes que puede hacer con esta restricción en .NET.
  2. Cada función que opera en un objeto común debe hacerlo locken una cosa común. Todos los bloqueos deben hacerse en el mismo orden. Esto hará que el hilo del código sea seguro, pero será increíblemente lento, y es mejor que no uses múltiples hilos.
  3. ...

No hay ninguna regla que haga que el hilo del código sea seguro, lo único que puede hacer es asegurarse de que su código funcione, no importa cuántas veces se ejecute activamente, cada hilo puede interrumpirse en cualquier momento, con cada hilo en su propio estado / ubicación, y esto para cada función (estática o no) que está accediendo a objetos comunes.

earlNameless
fuente
10
Las cerraduras no necesitan ser lentas. Las cerraduras son increíblemente rápidas; una cerradura sin oposición es del orden de magnitud de diez a cien nanosegundos . Las cerraduras disputadas son, por supuesto, arbitrariamente lentas; Si se ralentiza porque está impugnando los bloqueos, vuelva a diseñar el programa para eliminar la contención .
Eric Lippert
2
Supongo que no pude dejar el # 2 lo suficientemente claro. Estaba tratando de decir que hay una manera de hacer que los hilos sean seguros: coloque bloqueos alrededor de cada acceso de cualquier objeto común. Y suponiendo que haya múltiples hilos, generará contención y los bloqueos ralentizarán todo. Al agregar bloqueos, uno no puede simplemente agregar bloqueos a ciegas, sino que deben colocarse en lugares estratégicos muy buenos.
earlNameless