Usar una cadena como un candado para sincronizar los hilos

Mientras estaba mirando algún código de aplicación heredado noté que está usando un objeto de cadena para hacer la sincronización del hilo. Estoy tratando de resolver algunos problemas de contención de hilos en este progtwig y me preguntaba si esto podría provocar algunas situaciones extrañas. Alguna idea ?

private static string mutex= "ABC"; internal static void Foo(Rpc rpc) { lock (mutex) { //do something } } 

Cadenas como esa (del código) podrían ser ” internados “. Esto significa que todas las instancias de “ABC” apuntan al mismo objeto. Incluso en AppDomain s puede apuntar al mismo objeto (thx Steven para la sugerencia).

Si tiene muchos silenciosos de cadena, desde diferentes ubicaciones, pero con el mismo texto, todos podrían bloquear el mismo objeto.

El grupo interno conserva el almacenamiento de cadenas. Si asigna una constante de cadena literal a varias variables, cada variable se establece para hacer referencia a la misma constante en el grupo interno en lugar de hacer referencia a varias instancias diferentes de cadena que tienen valores idénticos.

Es mejor usar:

  private static readonly object mutex = new object(); 

Además, como su cadena no es const o de readonly , puede cambiarla. Entonces (en teoría) es posible bloquear su mutex. Cambie mutex a otra referencia y luego ingrese una sección crítica porque el locking usa otro objeto / referencia. Ejemplo:

 private static string mutex = "1"; private static string mutex2 = "1"; // for 'lock' mutex2 and mutex are the same private static void CriticalButFlawedMethod() { lock(mutex) { mutex += "."; // Hey, now mutex points to another reference/object // You are free to re-enter ... } } 

Para responder a su pregunta (como ya han hecho otros), existen algunos problemas potenciales con el ejemplo de código que proporcionó:

 private static string mutex= "ABC"; 
  • La variable mutex no es inmutable.
  • El literal de cadena "ABC" se referirá a la misma referencia de objeto interno en todas partes de su aplicación.

En general, recomendaría no bloquear cadenas. Sin embargo, hay un caso en el que me encontré donde es útil hacer esto.

Ha habido ocasiones en las que he mantenido un diccionario de objetos de locking donde la clave es algo único sobre algunos datos que tengo. Aquí hay un ejemplo artificial:

 void Main() { var a = new SomeEntity{ Id = 1 }; var b = new SomeEntity{ Id = 2 }; Task.Run(() => DoSomething(a)); Task.Run(() => DoSomething(a)); Task.Run(() => DoSomething(b)); Task.Run(() => DoSomething(b)); } ConcurrentDictionary _locks = new ConcurrentDictionary(); void DoSomething(SomeEntity entity) { var mutex = _locks.GetOrAdd(entity.Id, id => new object()); lock(mutex) { Console.WriteLine("Inside {0}", entity.Id); // do some work } } 

El objective de este tipo de código es serializar las invocaciones concurrentes de DoSomething() dentro del contexto de Id de la entidad. La desventaja es el diccionario. Cuantas más entidades hay, más grande se vuelve. También es solo más código para leer y pensar.

Creo que el internamiento de cadenas de .NET puede simplificar las cosas:

 void Main() { var a = new SomeEntity{ Id = 1 }; var b = new SomeEntity{ Id = 2 }; Task.Run(() => DoSomething(a)); Task.Run(() => DoSomething(a)); Task.Run(() => DoSomething(b)); Task.Run(() => DoSomething(b)); } void DoSomething(SomeEntity entity) { lock(string.Intern("dee9e550-50b5-41ae-af70-f03797ff2a5d:" + entity.Id)) { Console.WriteLine("Inside {0}", entity.Id); // do some work } } 

La diferencia aquí es que estoy confiando en el interinato de cadenas para darme la misma referencia de objeto por ID de entidad. Esto simplifica mi código porque no tengo que mantener el diccionario de instancias mutex.

Observe la cadena de UUID codificada que estoy usando como espacio de nombres. Esto es importante si elijo adoptar el mismo enfoque de locking en cadenas en otra área de mi aplicación.

El locking de cadenas puede ser una buena idea o una mala idea, según las circunstancias y la atención que el desarrollador presta a los detalles.

Si necesita bloquear una cadena, puede crear un objeto que empareje la cadena con un objeto con el que pueda bloquear.

 class LockableString { public string _String; public object MyLock; //Provide a lock to the data in. public LockableString() { MyLock = new object(); } }