2011-07-07 14 views
8

Recientemente tuve una pregunta de la entrevista en una prueba que fue similar a la de abajo, que no tienen mucha experiencia de desarrollo utilizando hilos por favor alguien puede ayudarme a asesorar a la forma de abordar esta cuestión ?:roscar pregunta de la prueba

public class StringQueue 
{ 
    private object _lockObject = new object(); 

    private List<string> _items = new List<string>(); 

    public bool IsEmpty() 
    { 
     lock (_lockObject) 
      return _items.Count == 0; 
    } 

    public void Enqueue(string item) 
    { 
     lock (_lockObject) 
      _items.Add(item); 
    } 

    public string Dequeue() 
    { 
     lock (_lockObject) 
     { 
      string result = _items[0]; 
      _items.RemoveAt(0); 

      return result; 
     } 
    } 
} 

¿El siguiente método es seguro con la implementación anterior y por qué?

public string DequeueOrNull() 
{ 
    if (IsEmpty()) 
     return null; 

    return Dequeue(); 
} 
+1

he etiquetado esta C# como parece. Por favor corrígeme si estoy equivocado. –

Respuesta

9

Me parece que la respuesta es no.

Mientras que el procedimiento Empty() bloquea el objeto, se libera tan pronto como se devuelve la llamada: un hilo diferente podría llamar a DequeueOrNull() entre la llamada a IsEmpty() y Dequeue() (en ese momento el objeto está desbloqueado), eliminando así el único elemento que existía, por lo que Dequeue() no era válido en ese momento.

Una solución plausible sería colocar el bloqueo sobre ambas instrucciones en DequeueOrNull(), por lo que ningún otro hilo podría llamar a DeQueue() después de la comprobación sino antes de DeQueue().

+0

Si el arreglo que escribió estaba en su lugar, 'ponga el candado sobre ambas declaraciones en DequeueOrNull()', ¿serían necesarias las cerraduras en las funciones individuales? – bitbucket

+0

@bitbucket: si quita las cerraduras de esas, toda la seguridad de subprocesos las garantías se romperán porque son métodos públicos. Si se les llama en el medio de DequeueOrNull, no les importará si se mantiene el bloqueo y pisotear las cosas. –

+0

Supongo que necesitarás bloquear un objeto diferente, de lo contrario, puede ser un punto muerto. Pero tal vez estoy equivocado, no estoy seguro de lo que sucede cuando las declaraciones de bloqueo están anidadas ... –

2

No, porque el estado de _items podría cambiar entre las IsEmpty() seguras para subprocesos y las llamadas Dequeue() seguras para subprocesos.

Fix con algo como lo siguiente, que asegura que _items está bloqueado durante toda la operación:

public string DequeueOrNull() 
{ 
    lock (_lockObject) 
    { 
    if (IsEmpty()) 
     return null; 

    return Dequeue(); 
    } 
} 

Nota: dependiendo de la implementation de _lock, es posible que desee evitar de doble bloqueo del recurso por moviendo las tripas de IsEmpty() y Dequeue en funciones auxiliares separadas.

+2

La instrucción 'lock' llama' Monitor.Enter' que es reentrante. –

+1

@Martinho a h, ya veo, gracias. Cuando respondí la pregunta, no estaba etiquetada con C# (y aunque supuse que era, creo que las preguntas de la entrevista se inclinan por ser independiente del idioma). Pensé que valía la pena mencionar el concepto semáforo/mutex, conceptos que son más importantes que las implementaciones específicas del lenguaje. –

3

No es seguro para la rosca. En la línea marcada es posible que el método de quitar de la cola se llama desde otro hilo y por lo tanto, la consiguiente quitar de la cola devolver un valor incorrecto:

public string DequeueOrNull() 
{ 
    if (IsEmpty()) 
     return null; 
/// << it is possible that the Dequeue is called from another thread here. 
    return Dequeue(); 
} 

El código de seguridad de rosca sería:

public string DequeueOrNull() 
{ 
    lock(_lockObject) { 
    if (IsEmpty()) 
     return null; 
    return Dequeue(); 
    } 
}