2012-01-19 6 views
5

Sé que es incorrecto usar lock(this) o cualquier objeto compartido.¿Este thread de uso de bloqueo es seguro?

Me pregunto si este uso está bien?

public class A 
{ 
    private readonly object locker = new object(); 
    private List<int> myList; 
    public A() 
    { 
    myList = new List<int>() 
    } 

    private void MethodeA() 
    { 
    lock(locker) 
    { 
     myList.Add(10); 
    } 
    } 

    public void MethodeB() 
    { 
    CallToMethodInOtherClass(myList); 
    } 
} 

public class OtherClass 
{ 
    private readonly object locker = new object(); 
    public CallToMethodInOtherClass(List<int> list) 
    { 
    lock(locker) 
    { 
    int i = list.Count; 
    } 
    } 
} 

¿Este subproceso es seguro? En OtherClass bloqueamos con un objeto privado, por lo que si el bloqueo class A con su bloqueo privado puede cambiar la lista en el bloque de bloqueo en OtherClass?

+11

Su baño tiene dos puertas, cada una con un candado. Su pregunta es "supongamos que solo cierro el primer candado cuando estoy en la ducha, y mi amigo Bob solo bloquea el segundo candado cuando está en la ducha. ¿Podemos ambos terminar en la ducha al mismo tiempo?" ¡Obviamente, sí! Si tú y Bob quieren evitar bañarse juntos, entonces debes aceptar usar * la misma cerradura *. No puede hacer que el acceso a un objeto sea seguro para hilos como este. –

Respuesta

10

No, no es seguro para subprocesos. Agregar y contar se pueden ejecutar en el "mismo" momento. Tienes dos objetos de bloqueo diferentes.

Siempre cierre su propio objeto de bloqueo al pasar la lista:

public void MethodeB() 
    { 
    lock(locker) 
    { 
     CallToMethodInOtherClass(myList); 
    } 
    } 
+0

En realidad es seguro para subprocesos, aunque solo como un detalle de implementación de 'Count' que es seguro para subprocesos, lo que no se promete, y no de una manera que se extendería a un código más realista. –

2

No, eso no es seguro para subprocesos.

Sus 2 métodos se bloquean en 2 objetos diferentes, no se bloquearán entre sí.

Dado que CallToMethodInOtherClass() solo recupera el valor de Contar, nada saldrá horriblemente mal. Pero el lock() que lo rodea es inútil y engañoso.

Si el método hiciera cambios en la lista, tendrías un problema desagradable. Para resolverlo, cambie MethodeB:

public void MethodeB() 
    { 
    lock(locker) // same instance as MethodA is using 
    { 
     CallToMethodInOtherClass(myList); 
    } 
    } 
+0

eso que pensé. Entonces, ¿puedes decirme cómo es el camino correcto? – Maya

2

No, tienen que bloquear el mismo objeto. Con su código, ambos se bloquean en una diferente y cada llamada se puede ejecutar simultáneamente.

Para que el código sea seguro, coloque un bloqueo en MethodeB o use la lista como objeto de bloqueo.

+0

@ Felix Creo que está mal encerrarlo con un objeto compartido, de modo que bloquear con la lista en sí es incorrecto. – Maya

+0

@Maya Eso es verdad, está mal. Pero es una manera fácil de resolver el problema descrito. Pero de todos modos, lo correcto sería crear una lista de subprocesos o un contenedor que contenga la lista y acceder a la lista solo sobre el contenedor. –

3

No, esto no es seguro para subprocesos. A.MethodeA y OtherClass.CallToMethodInOtherClass se bloquean en diferentes objetos, por lo que no son mutuamente excluyentes. Si necesita proteger el acceso a la lista, no la pase a código externo, manténgala en privado.

0

Probablemente la forma más fácil de hacer el truco

public class A 
{ 
    private List<int> myList; 
    public A() 
    { 
    myList = new List<int>() 
    } 

    private void MethodeA() 
    { 
    lock(myList) 
    { 
     myList.Add(10); 
    } 
    } 

    public void MethodeB() 
    { 
    CallToMethodInOtherClass(myList); 
    } 
} 

public class OtherClass 
{ 
    public CallToMethodInOtherClass(List<int> list) 
    { 
    lock(list) 
    { 
    int i = list.Count; 
    } 
    } 
} 
+0

pero pensé que era necesario bloquearlo con el objeto shraed – Maya

1

Culo todas las respuestas dicen que estos son diferentes objetos de bloqueo.

una forma sencilla es tener un f.ex objeto de bloqueo estático:

publc class A 
{ 
    public static readonly object lockObj = new object(); 
} 

y en ambas clases utilice el bloqueo como:

lock(A.lockObj) 
{ 
} 
3

Sin esto no es seguro para subprocesos. Para que sea seguro para los subprocesos, puede usar el bloqueo en los objetos static porque se comparten entre subprocesos, esto puede causar interbloqueos en el código, pero se puede controlar manteniendo el orden adecuado para el bloqueo. Hay un costo de rendimiento asociado con lock, así que úselo sabiamente.

Esperanza esto ayuda

+0

¿Cómo me puede ayudar esto? – Maya

+0

gracias, ahora entiendo – Maya

+0

Los objetos estáticos se comparten entre otros objetos, por lo que si pone el candado en esto, se consumirá por un solo objeto a la vez. Obtenga más información acerca de [static en MSDN] (http://msdn.microsoft.com/en-us/library/79b3xss3 (v = vs.80) .aspx) o [lock en MSDN] (http://msdn.microsoft) .com/en-us/library/c5kehkcz (v = vs.80) .aspx) (_La mejor práctica es definir un objeto privado para bloquear, o una variable de objeto estático privada para proteger datos comunes a todas las instancias. MSDN) –

0

Muchas de las respuestas han mencionado el uso de un bloqueo de sólo lectura estática.

Sin embargo, deberías tratar de evitar este bloqueo estático.Sería fácil crear un interbloqueo donde varios subprocesos usan el bloqueo estático.

Lo que podría utilizar en su lugar es una de las colecciones concurrentes .net 4, estas proporcionan sincronización de subprocesos en su nombre, para que no tenga que usar el bloqueo.

Eche un vistazo al espacio de nombres System.collections.Concurrent. Para este ejemplo, puede usar la clase ConcurrentBag<T>.

1

En realidad, es seguro para subprocesos (puramente como una cuestión de un detalle de implementación en Count), pero:

  1. fragmentos Hilo de seguridad de código Si no un hilo de seguridad de aplicaciones maquillaje. Puede combinar diferentes operaciones de seguridad de subprocesos en operaciones que no sean seguras para subprocesos. De hecho, gran parte del código no seguro para subprocesos se puede dividir en partes más pequeñas, todas las cuales son seguras para subprocesos por sí mismas.

  2. No es seguro para subprocesos por la razón por la que esperaba, lo que significa que extenderlo aún más no sería seguro para subprocesos.

Este código sería seguro para subprocesos:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    //note we've no locks! 
    int i = list.Count; 
    //do something with i but don't touch list again. 
} 

de llamadas con cualquier lista, y va a dar i un valor basado en el estado de esa lista, independientemente de lo que otros hilos son hasta. No dañará list. No dará i un valor no válido.

Así, mientras que el código también es seguro para subprocesos:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    Console.WriteLine(list[93]); // obviously only works if there's at least 94 items 
          // but that's nothing to do with thread-safety 
} 

Este código no sería seguro para subprocesos:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    lock(locker)//same as in the question, different locker to that used elsewhere. 
    { 
    int i = list.Count; 
    if(i > 93) 
     Console.WriteLine(list[93]); 
    } 
} 

Antes de seguir adelante, los dos bits que describen como thread- seguro no están prometidos por la especificación para la lista. La codificación conservadora supondría que no son seguros para subprocesos en lugar de depender de los detalles de implementación, pero voy a depender de los detalles de implementación porque afecta la cuestión de cómo usar bloqueos de una manera importante:

Porque no hay código que opera en list que no está adquiriendo el bloqueo en locker primero, no se impide que ese código se ejecute al mismo tiempo que CallToMethodInOtherClass. Ahora, mientras que list.Count es seguro para subprocesos y list[93] es seguro para la banda de rodadura, * la combinación de los dos depende del primero para garantizar que el segundo funciona no es seguro para subprocesos. Debido a que el código fuera del bloqueo puede afectar a list, es posible que el código llame al Remove o Clear entre Count asegurándonos que list[93] funcionaría y se llamaría al list[93].

Ahora, si sabemos que solo se agrega list, está bien, incluso si un cambio de tamaño se lleva a cabo al mismo tiempo, tendremos el valor de list[93] en ambos sentidos. Si algo está escribiendo en list[93] y es un tipo que .NET escribirá atómicamente (y int es uno de esos tipos), terminaremos con el antiguo o el nuevo, como si hubiéramos bloqueado correctamente Obtendría lo viejo o lo nuevo dependiendo de qué hilo vaya primero al candado.vez más, esto no es un detalle de implementación una promesa especificada, estoy declarando esto sólo para señalar cómo el hilo de seguridad dada todavía da lugar a código no seguro para subprocesos.

se mueve este hacia el código real. No debemos asumir que list.Countlist[93] y es multi-hilo, ya que no se nos prometió que serían y que podría cambiar, pero incluso si tuviéramos esa promesa, esas dos promesas no vamos a sumar a una promesa de que serían a prueba de hilos juntos.

Lo importante es utilizar la misma cerradura para proteger los bloques de código que pueden interferir entre sí. Por lo tanto, considera la variante más adelante que se garantiza que sea multi-hilo:

public class ThreadSafeList 
{ 
    private readonly object locker = new object(); 
    private List<int> myList = new List<int>(); 

    public void Add(int item) 
    { 
    lock(locker) 
     myList.Add(item); 
    } 
    public void Clear() 
    { 
    lock(locker) 
     myList.Clear(); 
    } 
    public int Count 
    { 
    lock(locker) 
     return myList.Count; 
    } 
    public int Item(int index) 
    { 
    lock(locker) 
     return myList[index]; 
    } 
} 

Esta clase se garantiza que sea multi-hilo en todo lo que hace. Sin depender de los detalles de implementación, no hay ningún método aquí que corrompa el estado o arroje resultados incorrectos debido a lo que está haciendo otro subproceso con la misma instancia. El código siguiente todavía no funciona sin embargo:

// (l is a ThreadSafeList visible to multiple threads. 
if(l.Count > 0) 
    Console.WriteLine(l[0]); 

Hemos garantizado el hilo de seguridad de cada llamada al 100%, pero no hemos garantizado la combinación, y que no puedo garantía la combinación.

Hay dos cosas que podemos hacer. Podemos agregar un método para la combinación. Algo parecido a lo siguiente sería común para muchas clases diseñadas específicamente para el uso de múltiples subprocesos:

public bool TryGetItem(int index, out int value) 
{ 
    lock(locker) 
    { 
    if(l.Count > index) 
    { 
     value = l[index]; 
     return true; 
    } 
    value = 0; 
    return false; 
    } 
} 

Esto hace que la prueba de recuento y la parte de recuperación de elemento de una sola operación que se garantiza que sea seguro para subprocesos.

Alternativamente, y más a menudo de lo que tenemos que hacer, tenemos la cerradura ocurra en el lugar donde se agrupan las operaciones:

lock(lockerOnL)//used by every other piece of code operating on l 
    if(l.Count > 0) 
    Console.WriteLine(l[0]); 

Por supuesto, esto hace que las cerraduras dentro ThreadSafeList y sólo una pérdida redundante de esfuerzo, espacio y tiempo. Esta es la razón principal por la que la mayoría de las clases no proporcionan seguridad de hilos a los miembros de su instancia, ya que no se pueden proteger significativamente grupos de llamadas de miembros dentro de la clase, es una pérdida de tiempo intentarlo a menos que las promesas de seguridad de hilos están muy bien especificados y son útiles por sí mismos.

volverse al código en su pregunta:

El bloqueo en CallToMethodInOtherClass debe ser eliminado a menos OtherClass tiene su propia razón para el bloqueo interno. No puede hacer una promesa significativa de que no se combinará de una manera no segura para los hilos y agregar más bloqueos a un programa solo aumenta la complejidad de analizarlo para asegurarse de que no haya interbloqueos.

La llamada a CallToMethodInOtherClass debe ser protegido por la misma cerradura como otras operaciones en esa clase:

public void MethodeB() 
{ 
    lock(locker) 
    CallToMethodInOtherClass(myList); 
} 

Entonces, siempre y cuando no almacena CallToMethodInOtherClassmyList algún lugar que pueda ser visto por otros hilos más adelante, no importa que CallToMethodInOtherClass no es seguro para subprocesos porque el único código que puede acceder myList trae su propia garantía de no llamar simultáneamente con otras operaciones en myList.

Las dos cosas importantes son:

  1. Cuando algo se describe como "thread-safe", sabe exactamente lo que es prometedor por eso, ya que hay diferentes tipos de promesa que caen bajo "thread-safe "y por sí solo significa" No pondré este objeto en un estado sin sentido ", que si bien es un componente importante, no es mucho por sí mismo.

  2. bloqueo en grupos de operaciones, con la misma cerradura para cada grupo que va a afectar a los mismos datos, y guardar el acceso a los objetos de modo que no puede posiblemente ser otro hilo no jugar a la pelota con esto.

* Esta es una definición muy limitada de subprocesos. Llamar al list[93] en un List<T> donde T es un tipo que se escribirá y leerá atómicamente y no sabemos si realmente tiene al menos 94 elementos, es igualmente seguro si hay otros subprocesos operando en él. Por supuesto, el hecho de que puede lanzar ArgumentOutOfRangeException en cualquier caso no es lo que la mayoría de la gente consideraría "seguro", pero la garantía que tenemos con múltiples hilos sigue siendo la misma que con uno. Es que obtenemos una garantía más sólida al marcar Count en un solo hilo, pero no en una situación de hilos múltiples que me lleva a describir que no es seguro para subprocesos; mientras que ese combo todavía no corromperá el estado, puede dar lugar a una excepción que nos aseguramos que no podría suceder.

+0

Use ConcurrentBag o ConcurrentDictionary en su lugar, entonces no tendrá que preocuparse por el bloqueo. –

+0

@ PålThingbø No es cierto en lo más mínimo. Primero, mientras esas colecciones, y de hecho mi propio [thread-safe dictionary and set] (https://bitbucket.org/JonHanna/ariadne) y algunas otras colecciones similares, son seguras para subprocesos para cada acción individual sobre ellas, grupos de acciones en ellos no son necesariamente seguros para subprocesos por las razones que doy más arriba (piénselo, 'int' es seguro para subprocesos, pero eso no hace que' ++ x' sea seguro para subprocesos, porque las tres acciones seguras para subprocesos que implicados no son seguros para subprocesos como una unidad). Raramente hay mucho que sugiera que alguien use una bolsa o un diccionario cuando quiera una lista; el ... –

+0

@ PålThingbø semántica de esas clases son completamente diferentes. Esperemos lanzar pronto una clase de lista de subprocesos seguros que ofrezca un comportamiento concurrente mucho mejor que el de la respuesta anterior, aunque seguirá siendo limitado (sin 'RemoveAt' o' Insert', podría producir una clase diferente que los tenga). demasiado tarde, pero hacerlos seguros para subprocesos sin bloquear es mucho más engorroso que el resto), y aún no hará que el código lo use mágicamente como un hilo seguro, como tampoco lo hace 'ConcurrentDictionary', nuevamente por las razones Doy arriba. –

Cuestiones relacionadas