Es una mala forma de usar this
en las declaraciones de bloqueo porque generalmente está fuera de su control quién más podría estar bloqueando ese objeto.
Para planificar adecuadamente las operaciones paralelas, se debe tener especial cuidado para considerar posibles situaciones de punto muerto, y tener un número desconocido de puntos de entrada de bloqueo dificulta esto. Por ejemplo, cualquiera con una referencia al objeto puede bloquearlo sin que el diseñador / creador del objeto lo sepa. Esto aumenta la complejidad de las soluciones de subprocesos múltiples y puede afectar su corrección.
Un campo privado suele ser una mejor opción, ya que el compilador impondrá restricciones de acceso y encapsulará el mecanismo de bloqueo. El uso this
viola la encapsulación al exponer parte de su implementación de bloqueo al público. Tampoco está claro que va a adquirir un bloqueo a this
menos que se haya documentado. Incluso entonces, confiar en la documentación para evitar un problema es subóptimo.
Finalmente, existe la idea errónea común que en lock(this)
realidad modifica el objeto pasado como parámetro, y de alguna manera lo hace de solo lectura o inaccesible. Esto es falso . El objeto pasado como parámetro lock
simplemente sirve como clave . Si ya se está reteniendo un candado en esa llave, no se puede hacer el candado; de lo contrario, se permite el bloqueo.
Esta es la razón por la cual es malo usar cadenas como claves en las lock
declaraciones, ya que son inmutables y son compartidas / accesibles a través de partes de la aplicación. En su lugar, debe usar una variable privada, una Object
instancia funcionará bien.
Ejecute el siguiente código C # como ejemplo.
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public void LockThis()
{
lock (this)
{
System.Threading.Thread.Sleep(10000);
}
}
}
class Program
{
static void Main(string[] args)
{
var nancy = new Person {Name = "Nancy Drew", Age = 15};
var a = new Thread(nancy.LockThis);
a.Start();
var b = new Thread(Timewarp);
b.Start(nancy);
Thread.Sleep(10);
var anotherNancy = new Person { Name = "Nancy Drew", Age = 50 };
var c = new Thread(NameChange);
c.Start(anotherNancy);
a.Join();
Console.ReadLine();
}
static void Timewarp(object subject)
{
var person = subject as Person;
if (person == null) throw new ArgumentNullException("subject");
// A lock does not make the object read-only.
lock (person.Name)
{
while (person.Age <= 23)
{
// There will be a lock on 'person' due to the LockThis method running in another thread
if (Monitor.TryEnter(person, 10) == false)
{
Console.WriteLine("'this' person is locked!");
}
else Monitor.Exit(person);
person.Age++;
if(person.Age == 18)
{
// Changing the 'person.Name' value doesn't change the lock...
person.Name = "Nancy Smith";
}
Console.WriteLine("{0} is {1} years old.", person.Name, person.Age);
}
}
}
static void NameChange(object subject)
{
var person = subject as Person;
if (person == null) throw new ArgumentNullException("subject");
// You should avoid locking on strings, since they are immutable.
if (Monitor.TryEnter(person.Name, 30) == false)
{
Console.WriteLine("Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string \"Nancy Drew\".");
}
else Monitor.Exit(person.Name);
if (Monitor.TryEnter("Nancy Drew", 30) == false)
{
Console.WriteLine("Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!");
}
else Monitor.Exit("Nancy Drew");
if (Monitor.TryEnter(person.Name, 10000))
{
string oldName = person.Name;
person.Name = "Nancy Callahan";
Console.WriteLine("Name changed from '{0}' to '{1}'.", oldName, person.Name);
}
else Monitor.Exit(person.Name);
}
}
Salida de la consola
'this' person is locked!
Nancy Drew is 16 years old.
'this' person is locked!
Nancy Drew is 17 years old.
Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string "Nancy Drew".
'this' person is locked!
Nancy Smith is 18 years old.
'this' person is locked!
Nancy Smith is 19 years old.
'this' person is locked!
Nancy Smith is 20 years old.
Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!
'this' person is locked!
Nancy Smith is 21 years old.
'this' person is locked!
Nancy Smith is 22 years old.
'this' person is locked!
Nancy Smith is 23 years old.
'this' person is locked!
Nancy Smith is 24 years old.
Name changed from 'Nancy Drew' to 'Nancy Callahan'.
lock(this)
es un consejo estándar; Es importante tener en cuenta que hacerlo generalmente hará que sea imposible que el código externo provoque que el bloqueo asociado con el objeto se retenga entre llamadas de método. Esto puede o no ser algo bueno . Existe cierto peligro al permitir que el código externo mantenga un bloqueo durante una duración arbitraria, y las clases generalmente deben diseñarse para hacer innecesario dicho uso, pero no siempre hay alternativas prácticas. Como un simple ejemplo, a menos que un aperos de recogida de unaToArray
oToList
método de su propia ...there is the common misconception that lock(this) actually modifies the object passed as a parameter, and in some way makes it read-only or inaccessible. This is false
- Creo que esas conversaciones son sobre el bit SyncBlock en el objeto CLR, así que formalmente es correcto - bloquear el objeto modificado en sí mismoPorque si las personas pueden llegar al
this
puntero de su instancia de objeto (es decir, su ), entonces también pueden intentar bloquear ese mismo objeto. Ahora es posible que no sepan que está bloqueandothis
internamente, por lo que esto puede causar problemas (posiblemente un punto muerto)Además de esto, también es una mala práctica, porque está bloqueando "demasiado"
Por ejemplo, puede tener una variable miembro de
List<int>
, y lo único que realmente necesita bloquear es esa variable miembro. Si bloquea todo el objeto en sus funciones, otras cosas que llaman a esas funciones se bloquearán esperando el bloqueo. Si esas funciones no necesitan acceder a la lista de miembros, hará que otro código espere y ralentice su aplicación sin ningún motivo.fuente
Eche un vistazo a la Sincronización de subprocesos de tema de MSDN (Guía de programación de C #)
fuente
Sé que este es un hilo viejo, pero debido a que la gente todavía puede buscarlo y confiar en él, parece importante señalar que
lock(typeof(SomeObject))
es significativamente peor que esolock(this)
. Una vez dicho esto; sinceras felicitaciones a Alan por señalar quelock(typeof(SomeObject))
es una mala práctica.Una instancia de
System.Type
es uno de los objetos más genéricos y de grano grueso que hay. Como mínimo, una instancia de System.Type es global para un AppDomain, y .NET puede ejecutar múltiples programas en un AppDomain. Esto significa que dos programas completamente diferentes podrían causar interferencia entre sí, incluso hasta el punto de crear un punto muerto si ambos intentan obtener un bloqueo de sincronización en la misma instancia de tipo.Por
lock(this)
lo tanto, no es una forma particularmente robusta, puede causar problemas y siempre debe levantar las cejas por todas las razones mencionadas. Sin embargo, existe un código ampliamente utilizado, relativamente respetado y aparentemente estable, como log4net, que usa ampliamente el patrón de bloqueo (este), a pesar de que personalmente preferiría ver ese cambio de patrón.Pero
lock(typeof(SomeObject))
abre una lata de gusanos completamente nueva y mejorada.Por lo que vale.
fuente
... y los mismos argumentos exactos se aplican a esta construcción también:
fuente
lock(this)
parezca particularmente lógico y conciso. Es un bloqueo terriblemente grueso, y cualquier otro código podría bloquear su objeto, lo que podría causar interferencia en su código interno. Tome cerraduras más granulares y asuma un control más estricto. Lo quelock(this)
sí tiene es que es mucho mejor quelock(typeof(SomeObject))
.Imagine que tiene una secretaria experta en su oficina que es un recurso compartido en el departamento. De vez en cuando, te apresuras hacia ellos porque tienes una tarea, solo para esperar que otro de tus compañeros de trabajo no los haya reclamado. Por lo general, solo tiene que esperar un breve período de tiempo.
Como cuidar es compartir, su gerente decide que los clientes también pueden usar la secretaria directamente. Pero esto tiene un efecto secundario: un cliente puede incluso reclamarlos mientras trabaja para este cliente y también los necesita para ejecutar parte de las tareas. Se produce un punto muerto, porque reclamar ya no es una jerarquía. Esto podría haberse evitado por completo al no permitir a los clientes reclamarlos en primer lugar.
lock(this)
Es malo como hemos visto. Un objeto externo puede bloquearse en el objeto y, dado que usted no controla quién está usando la clase, cualquiera puede bloquearlo ... Que es el ejemplo exacto como se describe anteriormente. Nuevamente, la solución es limitar la exposición del objeto. Sin embargo, si tiene unaprivate
,protected
ointernal
clase en la que ya se podía controlar quién está bloqueando en su objeto , porque está seguro de que usted ha escrito su código usted mismo. Entonces el mensaje aquí es: no lo exponga comopublic
. Además, garantizar que se use un bloqueo en situaciones similares evita los puntos muertos.Todo lo contrario de esto es bloquear los recursos que se comparten en todo el dominio de la aplicación, el peor de los casos. Es como poner a tu secretaria afuera y permitir que todos los reclamen. El resultado es un caos total, o en términos de código fuente: fue una mala idea; tíralo y comienza de nuevo. ¿Entonces cómo hacemos eso?
Los tipos se comparten en el dominio de la aplicación como la mayoría de las personas aquí señalan. Pero hay cosas aún mejores que podemos usar: cadenas. La razón es que las cadenas se agrupan . En otras palabras: si tiene dos cadenas que tienen el mismo contenido en un dominio de aplicación, existe la posibilidad de que tengan exactamente el mismo puntero. Dado que el puntero se usa como la tecla de bloqueo, lo que básicamente obtienes es un sinónimo de "prepararse para un comportamiento indefinido".
Del mismo modo, no debe bloquear objetos WCF, HttpContext.Current, Thread.Current, Singletons (en general), etc. ¿La forma más fácil de evitar todo esto?
private [static] object myLock = new object();
fuente
El bloqueo en este puntero puede ser malo si está bloqueando un recurso compartido . Un recurso compartido puede ser una variable estática o un archivo en su computadora, es decir, algo que se comparte entre todos los usuarios de la clase. La razón es que este puntero contendrá una referencia diferente a una ubicación en la memoria cada vez que se crea una instancia de su clase. Por lo tanto, bloquear esto en una instancia de una clase es diferente de bloquear esto en otra instancia de una clase.
Mira este código para ver a qué me refiero. Agregue el siguiente código a su programa principal en una aplicación de consola:
Crea una nueva clase como la siguiente.
Aquí hay una ejecución del programa bloqueando esto .
Aquí hay una ejecución del programa que se bloquea en myLock .
fuente
Random rand = new Random();
nvm, creo que veo que es el Balance repetidoHay un muy buen artículo al respecto http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects por Rico Mariani, arquitecto de rendimiento para el tiempo de ejecución de Microsoft® .NET
Extracto:
fuente
También hay una buena discusión sobre esto aquí: ¿Es este el uso adecuado de un mutex?
fuente
Aquí hay una ilustración mucho más simple (tomada de la Pregunta 34 aquí ) por qué el bloqueo (esto) es malo y puede provocar puntos muertos cuando el consumidor de su clase también intenta bloquear el objeto. A continuación, solo uno de los tres subprocesos puede continuar, los otros dos están bloqueados.
Para evitarlo, este tipo usó Thread. TryMonitor (con tiempo de espera) en lugar de bloquear:
https://blogs.appbeat.io/post/c-how-to-lock-without-deadlocks
fuente
SomeClass
, sigo teniendo el mismo punto muerto. Además, si el bloqueo en la clase principal se realiza en otro miembro de la instancia privada del Programa, se produce el mismo bloqueo. Por lo tanto, no estoy seguro si esta respuesta no es engañosa e incorrecta. Vea ese comportamiento aquí: dotnetfiddle.net/DMrU5hPorque cualquier fragmento de código que pueda ver la instancia de su clase también puede bloquear esa referencia. Desea ocultar (encapsular) su objeto de bloqueo para que solo el código que necesita referenciarlo pueda hacer referencia. La palabra clave this se refiere a la instancia de clase actual, por lo que cualquier cantidad de cosas podría tener referencia a ella y podría usarla para realizar la sincronización de subprocesos.
Para ser claros, esto es malo porque algún otro fragmento de código podría usar la instancia de clase para bloquear y podría impedir que su código obtenga un bloqueo oportuno o podría crear otros problemas de sincronización de subprocesos. Mejor caso: nada más utiliza una referencia a su clase para bloquear. Caso medio: algo usa una referencia a su clase para hacer bloqueos y causa problemas de rendimiento. El peor de los casos: algo usa una referencia de su clase para hacer bloqueos y causa problemas realmente malos, muy sutiles y difíciles de depurar.
fuente
Lo siento chicos, pero no puedo estar de acuerdo con el argumento de que bloquear esto podría causar un punto muerto. Estás confundiendo dos cosas: estancamiento y hambre.
Aquí hay una imagen que ilustra la diferencia.
Conclusión
Todavía puede usarlo con seguridad
lock(this)
si el hambre de hilo no es un problema para usted. Aún debe tener en cuenta que cuando el hilo, que está usando un hilo muerto de hambre,lock(this)
termina en una cerradura que tiene su objeto bloqueado, finalmente terminará en un hambre eterna;)fuente
lock(this)
, este tipo de código es simplemente incorrecto. Solo creo que llamarlo punto muerto es un poco abusivo.Consulte el siguiente enlace que explica por qué bloquear (esto) no es una buena idea.
http://blogs.msdn.com/b/bclteam/archive/2004/01/20/60719.aspx
Entonces, la solución es agregar un objeto privado, por ejemplo, lockObject a la clase y colocar la región de código dentro de la declaración de bloqueo como se muestra a continuación:
fuente
Aquí hay un código de muestra que es más fácil de seguir (IMO): ( Funcionará en LinqPad , referencia los siguientes espacios de nombres: System.Net y System.Threading.Tasks)
Algo para recordar es que el bloqueo (x) básicamente es azúcar sintáctico y lo que hace es usar Monitor.Enter y luego usa try, catch, finalmente block para llamar a Monitor.Exit. Ver: https://docs.microsoft.com/en-us/dotnet/api/system.threading.monitor.enter (sección de comentarios)
Salida
Tenga en cuenta que el hilo # 12 nunca termina ya que está completamente bloqueado.
fuente
DoWorkUsingThisLock
hilo no es necesario para ilustrar el problema?Puede establecer una regla que diga que una clase puede tener código que se bloquea en 'esto' o cualquier objeto que el código en la clase instancia. Por lo tanto, solo es un problema si no se sigue el patrón.
Si desea protegerse del código que no seguirá este patrón, entonces la respuesta aceptada es correcta. Pero si se sigue el patrón, no es un problema.
La ventaja del bloqueo (esto) es la eficiencia. ¿Qué sucede si tiene un simple "objeto de valor" que contiene un solo valor? Es solo una envoltura, y se instancia millones de veces. Al requerir la creación de un objeto de sincronización privado solo para el bloqueo, básicamente ha duplicado el tamaño del objeto y ha duplicado el número de asignaciones. Cuando el rendimiento importa, esta es una ventaja.
Cuando no le importa la cantidad de asignaciones o la huella de memoria, es preferible evitar el bloqueo (esto) por las razones indicadas en otras respuestas.
fuente
Habrá un problema si se puede acceder a la instancia públicamente porque podría haber otras solicitudes que podrían estar utilizando la misma instancia de objeto. Es mejor usar variable privada / estática.
fuente