Diferencia entre locking (casillero) y locking (variable_which_I_am_using)

Estoy usando C # & .NEt 3.5. ¿Cuál es la diferencia entre OptionA y OptionB?

class MyClass { private object m_Locker = new object(); private Dicionary m_Hash = new Dictionary(); public void OptionA() { lock(m_Locker){ // Do something with the dictionary } } public void OptionB() { lock(m_Hash){ // Do something with the dictionary } } } 

Estoy empezando a incursionar en threading (principalmente para crear un caché para una aplicación de subprocesos múltiples, NO utilizando la clase HttpCache, ya que no está adjuntado a un sitio web), y veo la syntax de OptionA en muchos de los ejemplos que ver en línea, pero no entiendo qué razón, si la hay, se hace sobre OptionB.

La opción B usa el objeto a proteger para crear una sección crítica. En algunos casos, esto comunica más claramente la intención. Si se usa de manera consistente, garantiza que solo una sección crítica para el objeto protegido estará activa a la vez:

 lock (m_Hash) { // Across all threads, I can be in one and only one of these two blocks // Do something with the dictionary } lock (m_Hash) { // Across all threads, I can be in one and only one of these two blocks // Do something with the dictionary } 

La opción A es menos restrictiva. Utiliza un objeto secundario para crear una sección crítica para el objeto a proteger. Si se utilizan múltiples objetos secundarios, es posible tener más de una sección crítica activa para el objeto protegido a la vez.

 private object m_LockerA = new object(); private object m_LockerB = new object(); lock (m_LockerA) { // It's possible this block is active in one thread // while the block below is active in another // Do something with the dictionary } lock (m_LockerB) { // It's possible this block is active in one thread // while the block above is active in another // Do something with the dictionary } 

La opción A es equivalente a la Opción B si usa solo un objeto secundario. En cuanto a la lectura del código, la intención de la Opción B es más clara. Si está protegiendo más de un objeto, la opción B no es realmente una opción.

Es importante comprender que el locking (m_Hash) NO evita que otro código use el hash. Solo evita que se ejecute otro código que también utiliza m_Hash como su objeto de locking.

Una razón para usar la opción A es que es probable que las clases tengan variables privadas que usará dentro de la instrucción de locking. Es mucho más fácil simplemente usar un objeto que use para bloquear el acceso a todos ellos en lugar de tratar de usar lockings de grano más finos para bloquear el acceso solo a los miembros que necesitará. Si intenta usar el método de grano más fino, probablemente tendrá que realizar varios lockings en algunas situaciones y luego deberá asegurarse de tomarlos siempre en el mismo orden para evitar interlockings.

Otra razón para usar la opción A es porque es posible que la referencia a m_Hash sea accesible fuera de su clase. Tal vez tenga una propiedad pública que le proporcione acceso, o tal vez la declare como protegida y las clases derivadas puedan usarla. En cualquier caso, una vez que el código externo tiene una referencia, es posible que el código externo lo use para un locking. Esto también abre la posibilidad de interlockings ya que no tiene forma de controlar o saber en qué orden se realizará el locking.

En realidad, no es buena idea bloquear el objeto si está usando sus miembros. Jeffrey Richter escribió en su libro “CLR via C #” que no hay garantía de que una clase de objeto que está utilizando para la sincronización no use lock(this) en su implementación (es interesante, pero era una forma recomendada de sincronización por Microsoft por algún tiempo … Luego, descubrieron que era un error), por lo que siempre es una buena idea usar un objeto separado especial para la sincronización. Entonces, como puede ver, OptionB no le dará una garantía de estancamiento: seguridad. Entonces, OptionA es mucho más seguro que OptionB.

No es lo que está “Bloqueando”, es el código que está contenido entre la cerradura {…} eso es importante y está impidiendo que se ejecute.

Si un hilo saca un candado () en cualquier objeto, evita que otros hilos obtengan un locking en el mismo objeto y, por lo tanto, evita que el segundo hilo ejecute el código entre los paréntesis.

Entonces, esa es la razón por la cual la mayoría de las personas simplemente crea un objeto basura para bloquear, evita que otros subprocesos obtengan un locking en ese mismo objeto basura.

Creo que el scope de la variable que “aprueba” determinará el scope del locking. es decir, una variable de instancia será con respecto a la instancia de la clase, mientras que una variable estática será para todo el dominio de la aplicación.

Al observar la implementación de las colecciones (utilizando Reflector), el patrón parece seguir que una variable de instancia llamada SyncRoot se declara y usa para todas las operaciones de locking con respecto a la instancia de la colección.

Bueno, depende de lo que quiere bloquear (se hará seguro).

Normalmente, elegiría la opción B para proporcionar acceso a threadsafe a m_Hash SOLAMENTE. Donde como OptionA, usaría para bloquear el tipo de valor, que no se puede usar con el locking, o tengo un grupo de objetos que necesitan bloquearse simultáneamente, pero no sé qué bloquear toda la instancia usando el lock(this)

Bloquear el objeto que está utilizando es simplemente una cuestión de conveniencia. Un objeto de locking externo puede simplificar las cosas, y también es necesario si el recurso compartido es privado, como con una colección (en cuyo caso utiliza el objeto ICollection.SyncRoot ).

La opción A es la manera de ir aquí, siempre y cuando en todo su código, cuando acceda al m_hash, use m_Locker para bloquearlo.

Ahora imagina este caso. Bloquea el objeto. Y ese objeto en una de las funciones que llama tiene un segmento de código de lock(this) . En este caso, ese es un punto muerto irrecuperable seguro