En realidad, es seguro para subprocesos (puramente como una cuestión de un detalle de implementación en Count
), pero:
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.
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.Count
list[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 CallToMethodInOtherClass
myList
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:
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.
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.
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. –