2010-08-09 33 views
7

Estoy confundido por un listado de código en un libro que estoy leyendo, C# 3 en una Cáscara de nuez, en el enhebrado. En el tema sobre el hilo de seguridad en los servidores de aplicaciones, por debajo de código se da como un ejemplo de un UserCache:Por qué bloquear cuando leo desde un diccionario

static class UserCache 
{ 
    static Dictionary< int,User> _users = new Dictionary< int, User>(); 

    internal static User GetUser(int id) 
    { 
     User u = null; 

     lock (_users) // Why lock this??? 
      if (_users.TryGetValue(id, out u)) 
       return u; 

     u = RetrieveUser(id); //Method to retrieve from databse 

     lock (_users) _users[id] = u; //Why lock this??? 
      return u; 
    } 
} 

Los autores explican por qué el método RetrieveUser no está en un bloqueo, esto es para evitar el bloqueo de la caché para un período más largo.
No estoy seguro de por qué bloquear TryGetValue y la actualización del diccionario, ya que incluso con lo anterior, el diccionario se actualiza dos veces si 2 hilos llaman simultáneamente con el mismo ID no recuperado.

¿Qué se logra bloqueando la lectura del diccionario?
Muchas gracias de antemano por todos sus comentarios y opiniones.

Respuesta

14

Dictionary<TKey, TValue> clase is not threadsafe.

Si un hilo escribe una tecla en el diccionario mientras un hilo diferente lee el diccionario, puede estropearse. (Por ejemplo, si la operación de escritura desencadena un cambio de tamaño de la matriz, o si las dos teclas son una colisión hash)

Por lo tanto, el código utiliza un bloqueo para evitar las escrituras concurrentes.

+0

Gracias! Eso tiene sentido. Supongo que realmente no hay forma de probarlo de manera determinista. Traté de quitar los bloqueos y lo llamé en un bucle durante miles de veces creando nuevos hilos, después de cambiar el código para que cada hilo siempre escriba después de la lectura, pero funcionó bien. Pero como con la mayoría de los hilos, el hecho de que funcionó para mí no significa que siempre funcionará. ¡Aclamaciones! – eastender

+0

¿Todavía tengo que bloquearlo cuando el diccionario se establece una sola vez y luego solo se leen varios hilos? Probablemente no, pero otra opinión será útil – Adassko

+0

No; múltiples lecturas están bien. – SLaks

1

Eso es una práctica común para acceder a cualquier tema no estructuras seguras como listas, diccionarios, valores comunes y compartidos, etc.

Y respondiendo a la pregunta principal: el bloqueo de una lectura garantizamos que el diccionario no será cambiado por otro hilo, mientras estamos leyendo su valor Esto no está implementado en el diccionario y es por eso que se llama no seguro para hilos :)

4

Hay una condición de carrera benigna cuando escribe en el diccionario; es posible, como dijiste, que dos hilos determinen que no hay una entrada coincidente en el caché. En este caso, ambos leerán del DB e intentarán insertarlo. Solo se guarda el objeto insertado por el último hilo; el otro objeto será basura recolectada cuando el primer hilo termine con él.

leído al diccionario debe estar bloqueado porque otro hilo puede estar escribiendo al mismo tiempo, y la lectura debe buscar en una estructura coherente.

Tenga en cuenta que el ConcurrentDictionary introducido en .NET 4.0 reemplaza bastante este tipo de modismo.

0

Si dos hilos llaman simultáneamente y existe la identificación, ambos devolverán la información de usuario correcta. El primer bloqueo es para evitar errores, como dijo SLaks: si alguien está escribiendo en el diccionario mientras intenta leerlo, tendrá problemas. En este escenario, nunca se alcanzará el segundo bloqueo.

Si dos hilos llaman simultáneamente y la identificación no existe, un hilo se bloqueará e introducirá TryGetValue, esto devolverá falso y le dará un valor predeterminado. Este primer bloqueo es nuevamente, para evitar los errores descritos por SLaks. En este punto, ese primer subproceso liberará el bloqueo y el segundo subirá y hará lo mismo. Ambos configurarán 'u' la información de 'RetrieveUser (id)'; esta debería ser la misma información. Un hilo bloqueará el diccionario y asignará _users [id] al valor de u. Este segundo bloqueo es para que dos hilos intenten escribir valores en las mismas ubicaciones de memoria simultáneamente y corrompan esa memoria. No sé qué hará el segundo subproceso cuando entre a la tarea.Regresará antes ignorando la actualización o sobrescribirá los datos existentes del primer hilo. De todos modos, el diccionario contendrá la misma información porque ambos hilos deberían haber recibido los mismos datos en 'u' de RetrieveUser.

Para el rendimiento, el autor comparó dos escenarios: el escenario anterior, que será extremadamente raro y el bloqueo mientras que dos hilos intentan y escriben los mismos datos, y el segundo donde es mucho más probable que dos hilos soliciten datos para un objeto que necesita ser escrito, y uno que existe. Por ejemplo, threadA y threadB llaman simultáneamente y ThreadA se bloquea para una identificación que no existe. No hay ninguna razón para hacer threadB espere una búsqueda mientras threadA está trabajando en RetriveUser. Esta situación es probablemente mucho más probable que los identificadores duplicados descritos anteriormente, por lo que para el rendimiento el autor optó por no bloquear todo el bloque.

Cuestiones relacionadas