2009-04-15 14 views
12

Todavía estoy confundido ... Cuando escribimos algo como esto:¿Bloquea() {} bloquea un recurso o bloquea un fragmento de código?

Object o = new Object(); 
var resource = new Dictionary<int , SomeclassReference>(); 

... y tienen dos bloques de código que se cierran o mientras se accede a resource ...

//Code one 
lock(o) 
{ 
    // read from resource  
} 

//Code two 
lock(o) 
{ 
    // write to resource 
} 

Ahora, si tengo dos hilos, con un hilo que ejecuta el código que lee de resource y otro escrito en él, me gustaría bloquear resource de modo que cuando se está leyendo, el escritor tendría que esperar (y viceversa - si se está escribiendo, los lectores tendrían que esperar). ¿La construcción de bloqueo me ayudará? ... o debería usar algo más?

(estoy usando Dictionary para los fines de este ejemplo, pero podría ser cualquier cosa)

Hay dos casos Estoy preocupado específicamente:

  1. dos hilos que tratan de ejecutar misma línea de código
  2. dos hilos que tratan de trabajar en el mismo recurso

Will lock ayuda en ambas condiciones?

+0

@SilverHorse: perdón por pisar fuerte en su edición, volvió a agregar una etiqueta y no se dio cuenta de que había editado mientras tanto! – Shog9

Respuesta

12

La mayoría de las demás respuestas abordan el ejemplo del código, por lo que intentaré responder su pregunta en el título.

Un candado es solo una ficha. Quien tenga la ficha puede subir al escenario, por así decirlo. Por lo tanto, el objeto que está bloqueando no tiene una conexión explícita con el recurso con el que intenta sincronizar. Siempre y cuando todos los lectores/escritores estén de acuerdo en lo mismo, puede ser cualquier cosa.

Al intentar bloquear un objeto (es decir, llamando al Monitor.Enter en un objeto), el tiempo de ejecución comprueba si el bloqueo ya está retenido por un hilo. Si este es el caso, el hilo que intenta bloquear se suspende, de lo contrario, adquiere el bloqueo y procede a ejecutarlo.

Cuando un hilo que contiene un bloqueo sale del ámbito de bloqueo (es decir, llama a Monitor.Exit), se libera el bloqueo y los hilos de espera pueden adquirir el bloqueo.

Por último, un par de cosas a tener en cuenta en relación con las cerraduras:

  • bloqueo, siempre y cuando es necesario, pero no más.
  • Si usa Monitor.Enter/Exit en lugar de la palabra clave lock, asegúrese de realizar la llamada al Exit en un bloque finally para liberar el bloqueo incluso en el caso de una excepción.
  • La exposición del objeto al bloqueo hace que sea más difícil obtener una visión general de quién está bloqueando y cuándo. Las operaciones idealmente sincronizadas deben estar encapsuladas.
+1

no entendió esto "Exponer el objeto a bloquear hace que sea más difícil obtener una visión general de quién está bloqueando y cuándo. Operaciones idealmente sincronizadas deben ser encapsuladas" Gracias –

+0

Si el objeto utilizado para el bloqueo es accesible fuera del tipo, se vuelve más difícil identificar quién está bloqueado. Por otro lado, si todo el bloqueo está encapsulado, puede obtener fácilmente una descripción general del bloqueo. –

+0

no está claro. ¿Quieres decir que el objeto 'o' que he usado está estrictamente dentro de un tipo correcto? –

0

Y eso debería funcionar suponiendo que solo tiene un proceso involucrado. Querrá utilizar un "Mutex" si quiere que funcione en más de un proceso.

Ah, y el objeto "o", debe ser un singleton o delimitado donde sea que se necesite bloquear, ya que lo que REALMENTE está bloqueado es ese objeto y si creas uno nuevo, entonces ese nuevo no será bloqueado aún.

2

Ambos bloques de código están bloqueados aquí. Si el hilo uno bloquea el primer bloque, y el hilo dos intentos para llegar al segundo bloque, tendrá que esperar.

+0

Hay dos casos ... 1) dos thredas tratando de ejecutar misma línea de código 2) dos hilos que tratan de trabajar en el mismo recurso bloqueará ayuda en ambas condiciones –

5

Sí, usar un candado es el camino correcto a seguir. Puede bloquear cualquier objeto, pero como se menciona en otras respuestas, bloquear su propio recurso es probablemente el más fácil y seguro.

Sin embargo, es posible que desee utilizar un par de bloqueo de lectura/escritura en lugar de solo un bloqueo, para disminuir la sobrecarga de simultaneidad.

La razón de esto es que si solo tiene una escritura de subproceso, pero varios subprocesos de lectura, no desea una operación de lectura para bloquear otra operación de lectura, sino solo un bloque de lectura una escritura o viceversa.

Ahora, soy más un chico de Java, así que tendrás que cambiar la sintaxis y desenterrar algunos documentos para aplicarlos en C#, pero rw-locks son parte del paquete de simultaneidad estándar en Java, por lo que podrías escribir algo como :

public class ThreadSafeResource<T> implements Resource<T> { 
    private final Lock rlock; 
    private final Lock wlock; 
    private final Resource res; 

    public ThreadSafeResource(Resource<T> res) { 
     this.res = res; 
     ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
     this.rlock = rwl.readLock(); 
     this.wlock = rwl.writeLock(); 
    } 

    public T read() { 
     rlock.lock(); 
     try { return res.read(); } 
     finally { rlock.unlock(); } 
    } 

    public T write(T t) { 
     wlock.lock(); 
     try { return res.write(t); } 
     finally { wlock.unlock(); } 
    } 
} 

Si alguien puede llegar a un código de ejemplo de C# ...

+0

alguna idea de cómo aplicar ese lectura-escritura par de bloqueo? –

+3

Bloquear el recurso no es una buena idea si ese recurso es visible fuera de su código interno. Eso podría conducir a estancamientos o condiciones de carrera. se recomienda que bloquee un objeto privado para evitar ese escenario. –

+0

Bueno, en ese caso, * desea * evitar que otro código compatible con concurrencia estropee su recurso (no seguro para subprocesos) mientras está haciendo cosas en él, por lo que * desea * bloquearlo. – Varkhan

0

La forma en que lo tienes en práctica es una forma aceptable de hacer lo que tiene que hacer. Una forma de mejorar su forma de hacer esto sería usar lock() en el diccionario mismo, en lugar de usar un segundo objeto para sincronizar el diccionario. De esta forma, en lugar de pasar alrededor de un objeto adicional, el recurso en sí mismo realiza un seguimiento de si hay un bloqueo en su propio monitor.

El uso de un objeto separado puede ser útil en algunos casos, como sincronizar el acceso a recursos externos, pero en casos como este se trata de una sobrecarga.

2

El bloqueo (o) {...} declaración se compila a esto:

Monitor.Enter(o) 
try { ... } 
finally { Monitor.Exit(o) } 

La llamada a Monitor.Enter() bloqueará el hilo si otro hilo ya lo ha llamado. Solo se desbloqueará después de que el otro hilo haya llamado a Monitor.Exit() en el objeto.

2

¿Bloquear ayuda en ambas condiciones? Sí.

¿Bloquea() {} bloquea un recurso o bloquea un fragmento de código?

lock(o) 
{ 
    // read from resource  
} 

es el azúcar sintáctica para

Monitor.Enter(o); 
try 
{ 
    // read from resource 
} 
finally 
{ 
    Monitor.Exit(o); 
} 

El clase monitor alberga la colección de objetos que está utilizando para sincronizar el acceso a los bloques de código. Para cada objeto de sincronización, Monitor de mantiene:

  1. Una referencia a la rosca que en la actualidad mantiene el bloqueo sobre el objeto de sincronización; es decir, es el turno de este hilo para ejecutar.
  2. Una cola "lista": la lista de subprocesos que se bloquean hasta que se les da el bloqueo para este objeto de sincronización.
  3. A "espera" cola - la lista de temas que bloquean hasta su traslado a la cola "listo" por Monitor.Pulse() o Monitor.PulseAll().

Entonces, cuando un hilo llama a lock (o), se coloca en la cola lista de o, hasta que se le da el bloqueo en o, en ese momento continúa ejecutando su código.

+0

Debería ser 'finalmente' y no 'atrapar' seguramente. Lo siento nitpick :) – Andy

+0

@Andy - Sí. Respuesta actualizada – mbeckish