etiquetas etiqueta definicion body c# multithreading design-patterns concurrency

c# - definicion - etiqueta title



¿Para qué sirve el patrón SyncRoot? (6)

Aquí hay otra cosa interesante relacionada con este tema:

Valor cuestionable de SyncRoot en colecciones (por Brad Adams) :

Notará una propiedad SyncRoot en muchas de las colecciones en System.Collections. En retrospectiva, creo que esta propiedad fue un error. Krzysztof Cwalina, un gerente de programa en mi equipo, me acaba de enviar algunas reflexiones sobre por qué es eso - estoy de acuerdo con él:

Encontramos que las API de sincronización basadas en SyncRoot no son lo suficientemente flexibles para la mayoría de los escenarios. Las API permiten el acceso seguro de subprocesos a un único miembro de una colección. El problema es que hay numerosos escenarios en los que debe bloquear varias operaciones (por ejemplo, eliminar un elemento y agregar otro). En otras palabras, generalmente es el código el que usa una colección que desea elegir (y puede implementar) la política de sincronización correcta, no la colección en sí misma. Descubrimos que SyncRoot se usa muy raramente y en los casos en que se usa, en realidad no agrega mucho valor. En los casos en que no se utiliza, es solo una molestia para los implementadores de ICollection.

Tenga la seguridad de que no cometeremos el mismo error ya que construimos las versiones genéricas de estas colecciones

Estoy leyendo ac # libro que describe el patrón SyncRoot. Muestra

void doThis() { lock(this){ ... } } void doThat() { lock(this){ ... } }

y se compara con el patrón SyncRoot:

object syncRoot = new object(); void doThis() { lock(syncRoot ){ ... } } void doThat() { lock(syncRoot){ ... } }

Sin embargo, realmente no entiendo la diferencia aquí; parece que en ambos casos solo se puede acceder a ambos métodos por un hilo a la vez.

El libro describe ... porque el objeto de la instancia también se puede usar para el acceso sincronizado desde el exterior y no se puede controlar esta forma de la clase en sí, se puede usar el patrón SyncRoot ¿Eh? ''objeto de la instancia''?

¿Alguien puede decirme la diferencia entre los dos enfoques anteriores?

Gracias por adelantado


Aquí hay un ejemplo :

class ILockMySelf { public void doThat() { lock (this) { // Don''t actually need anything here. // In this example this will never be reached. } } } class WeveGotAProblem { ILockMySelf anObjectIShouldntUseToLock = new ILockMySelf(); public void doThis() { lock (anObjectIShouldntUseToLock) { // doThat will wait for the lock to be released to finish the thread var thread = new Thread(x => anObjectIShouldntUseToLock.doThat()); thread.Start(); // doThis will wait for the thread to finish to release the lock thread.Join(); } } }

Verá que la segunda clase puede usar una instancia de la primera en una instrucción de bloqueo. Esto lleva a un punto muerto en el ejemplo.

La implementación correcta de SyncRoot es:

object syncRoot = new object(); void doThis() { lock(syncRoot ){ ... } } void doThat() { lock(syncRoot ){ ... } }

como syncRoot es un campo privado, no tiene que preocuparse por el uso externo de este objeto.


El propósito real de este patrón es implementar la sincronización correcta con la jerarquía de envolturas.

Por ejemplo, si la clase WrapperA envuelve una instancia de ClassThanNeedsToBeSynced, y la clase WrapperB ajusta la misma instancia de ClassThanNeedsToBeSynced, no se puede bloquear en WrapperA o WrapperB, ya que si se bloquea en WrapperA, el bloqueo en WrappedB no esperará. Por este motivo, debe bloquear en wrapperAInst.SyncRoot y wrapperBInst.SyncRoot, que delegan el bloqueo en uno de ClassThanNeedsToBeSynced.

Ejemplo:

public interface ISynchronized { object SyncRoot { get; } } public class SynchronizationCriticalClass : ISynchronized { public object SyncRoot { // you can return this, because this class wraps nothing. get { return this; } } } public class WrapperA : ISynchronized { ISynchronized subClass; public WrapperA(ISynchronized subClass) { this.subClass = subClass; } public object SyncRoot { // you should return SyncRoot of underlying class. get { return subClass.SyncRoot; } } } public class WrapperB : ISynchronized { ISynchronized subClass; public WrapperB(ISynchronized subClass) { this.subClass = subClass; } public object SyncRoot { // you should return SyncRoot of underlying class. get { return subClass.SyncRoot; } } } // Run class MainClass { delegate void DoSomethingAsyncDelegate(ISynchronized obj); public static void Main(string[] args) { SynchronizationCriticalClass rootClass = new SynchronizationCriticalClass(); WrapperA wrapperA = new WrapperA(rootClass); WrapperB wrapperB = new WrapperB(rootClass); // Do some async work with them to test synchronization. //Works good. DoSomethingAsyncDelegate work = new DoSomethingAsyncDelegate(DoSomethingAsyncCorrectly); work.BeginInvoke(wrapperA, null, null); work.BeginInvoke(wrapperB, null, null); // Works wrong. work = new DoSomethingAsyncDelegate(DoSomethingAsyncIncorrectly); work.BeginInvoke(wrapperA, null, null); work.BeginInvoke(wrapperB, null, null); } static void DoSomethingAsyncCorrectly(ISynchronized obj) { lock (obj.SyncRoot) { // Do something with obj } } // This works wrong! obj is locked but not the underlaying object! static void DoSomethingAsyncIncorrectly(ISynchronized obj) { lock (obj) { // Do something with obj } } }


Otro ejemplo concreto:

class Program { public class Test { public string DoThis() { lock (this) { return "got it!"; } } } public delegate string Something(); static void Main(string[] args) { var test = new Test(); Something call = test.DoThis; //Holding lock from _outside_ the class IAsyncResult async; lock (test) { //Calling method on another thread. async = call.BeginInvoke(null, null); } async.AsyncWaitHandle.WaitOne(); string result = call.EndInvoke(async); lock (test) { async = call.BeginInvoke(null, null); async.AsyncWaitHandle.WaitOne(); } result = call.EndInvoke(async); } }

En este ejemplo, la primera llamada tendrá éxito, pero si rastrea en el depurador, la llamada a DoSomething se bloqueará hasta que se libere el bloqueo. La segunda llamada se bloqueará, ya que el hilo principal está sujetando el bloqueo del monitor en la prueba .

El problema es que Main puede bloquear la instancia del objeto, lo que significa que puede evitar que la instancia haga algo que el objeto crea que debería sincronizarse. El punto es que el objeto en sí mismo sabe lo que requiere el bloqueo, y la interferencia externa es solo la búsqueda de problemas. Es por eso que el patrón de tener una variable de miembro privado que puede usar exclusivamente para la sincronización sin tener que preocuparse por la interferencia externa.

Lo mismo aplica para el patrón estático equivalente:

class Program { public static class Test { public static string DoThis() { lock (typeof(Test)) { return "got it!"; } } } public delegate string Something(); static void Main(string[] args) { Something call =Test.DoThis; //Holding lock from _outside_ the class IAsyncResult async; lock (typeof(Test)) { //Calling method on another thread. async = call.BeginInvoke(null, null); } async.AsyncWaitHandle.WaitOne(); string result = call.EndInvoke(async); lock (typeof(Test)) { async = call.BeginInvoke(null, null); async.AsyncWaitHandle.WaitOne(); } result = call.EndInvoke(async); } }

Use un objeto estático privado para sincronizar, no el Tipo.


Si tiene una estructura de datos interna a la que desea impedir el acceso simultáneo por varios hilos, siempre debe asegurarse de que el objeto que está bloqueando no sea público.

El razonamiento detrás de esto es que un objeto público puede ser bloqueado por cualquier persona y, por lo tanto, puede crear interbloqueos porque no tiene el control total del patrón de bloqueo.

Esto significa que bloquear this no es una opción, ya que cualquiera puede bloquear ese objeto. Del mismo modo, no debe encerrarse en algo que expone al mundo exterior.

Lo que significa que la mejor solución es usar un objeto interno, y por lo tanto, la sugerencia es simplemente usar Object .

El bloqueo de las estructuras de datos es algo sobre lo que realmente necesita tener control total, de lo contrario correrá el riesgo de configurar un bloqueo de interbloqueo, que puede ser muy problemático de manejar.


Vea this artículo de Jeff Richter. Más específicamente, este ejemplo que demuestra que bloquear en "esto" puede causar un interbloqueo:

using System; using System.Threading; class App { static void Main() { // Construct an instance of the App object App a = new App(); // This malicious code enters a lock on // the object but never exits the lock Monitor.Enter(a); // For demonstration purposes, let''s release the // root to this object and force a garbage collection a = null; GC.Collect(); // For demonstration purposes, wait until all Finalize // methods have completed their execution - deadlock! GC.WaitForPendingFinalizers(); // We never get to the line of code below! Console.WriteLine("Leaving Main"); } // This is the App type''s Finalize method ~App() { // For demonstration purposes, have the CLR''s // Finalizer thread attempt to lock the object. // NOTE: Since the Main thread owns the lock, // the Finalizer thread is deadlocked! lock (this) { // Pretend to do something in here... } } }