2012-01-12 14 views
6

¿Cuáles son algunas de las razones por las cuales escribir la siguiente pieza de código se considera una mala práctica?¿Por qué bloquear en lugar de buclear?

while (someList.isEmpty()) { 
    try { 
     Thread.currentThread().sleep(100); 
    } 
    catch (Exception e) {} 
    } 
    // Do something to the list as soon as some thread adds an element to it. 

Para mí, recogiendo un valor arbitrario a dormir no es una buena práctica, y me gustaría utilizar un BlockingQueue en esta situación, pero me gustaría saber si hay más de una razón por la cual uno no debe escribe tal código

Respuesta

6

Impone un retraso promedio de 50 milisegundos antes de que se actúe sobre el evento, y se activa 10 veces por segundo cuando no hay ningún evento que manejar. Si ninguna de esas cosas es particularmente importante, entonces no es nada elegante.

+0

Solo me pregunto, ¿no es así como las funciones de espera funcionan internamente de todos modos? (Excluyendo obviamente la excepción) – Mehrdad

+1

@ Mehrdad- Típicamente no. Por lo general, los hilos se colocan en una "cola de espera" y no reciben ningún tiempo de procesador. Cuando ocurre algún evento que los despierte, se colocan de nuevo en la cola de ejecución para que se programen. Esto significa que puede tener un millón de hilos inactivos sin pérdida de rendimiento si solo dos o tres hilos están activos al mismo tiempo. – templatetypedef

+0

@templatetypedef: Entonces, ¿cómo exactamente el sistema operativo averiguar si los hilos se deben despertar en un segmento de tiempo determinado? ¿No debería verificar el estado del hilo en un bucle en cada segmento de tiempo? – Mehrdad

1

Hay muchas razones para no hacer esto. En primer lugar, como ha notado, esto significa que puede haber un gran retraso entre el momento en que ocurre un evento al que el hilo debe responder y el tiempo de respuesta real, ya que el hilo puede estar durmiendo. En segundo lugar, dado que cualquier sistema tiene solo muchos procesadores diferentes, si tiene que seguir eliminando hilos importantes de un procesador para que puedan decirle al hilo que se vaya a dormir en otro momento, disminuirá la cantidad total de trabajo útil realizado por el sistema y aumentar el uso de energía del sistema (que es importante en sistemas como teléfonos o dispositivos integrados).

0

también introduce condiciones de carrera para su clase. si estaba usando una cola de bloqueo en lugar de una lista normal, el hilo se bloquearía hasta que haya una nueva entrada en la lista. En su caso, un segundo hilo podría poner y obtener un elemento de la lista mientras dure su hilo de trabajo y ni siquiera lo notaría.

0

Para añadir a las otras respuestas, también tienen una condición de carrera si usted tiene más de un hilo eliminación de elementos de la cola:

  1. cola está vacía
  2. hilo Un pone un elemento en la cola
  3. thread B comprueba si la cola está vacía; no es
  4. thread C comprueba si la cola está vacía; no es
  5. El hilo B toma de la cola; éxito
  6. thread C toma de la cola; insuficiencia

Se podía manejar esto por atómicamente (dentro de un bloque synchronized) comprobando si la cola está vacía y, si y sólo si no es así, tomando un elemento de ella; Ahora el bucle se parece a un feo cabello:

T item; 
while ((item = tryTake(someList)) == null) { 
    try { 
     Thread.currentThread().sleep(100); 
    } 
    catch (InterruptedException e) { 
     // it's almost never a good idea to ignore these; need to handle somehow 
    } 
} 
// Do something with the item 

synchronized private T tryTake(List<? extends T> from) { 
    if (from.isEmpty()) return null; 
    T result = from.remove(0); 
    assert result != null : "list may not contain nulls, which is unfortunate" 
    return result; 
} 

o usted podría tener sólo utilizó un BlockingQueue.

+0

¿No quieres decir 'eliminar (0)'? Supongo que no quieres ignorar el primer elemento. –

+0

erm sí, error de escritura, Reparar ahora. – yshavit

+0

En su descripción se refiere a una "cola" pero en su código utiliza una "Lista". Esto podría ser confuso. Puede usar 'Queue.remove()'; Por cierto, una LinkedList también es una cola. –

1

El ciclo es un excelente ejemplo de lo que no debe hacer. ;)


Thread.currentThread().sleep(100); 

No hay necesidad de obtener la currentThread() ya que este es un método estático. Es lo mismo que

Thread.sleep(100); 

catch (Exception e) {} 

Esto es muy mala práctica.Tan malo que no te sugiero que pongas esto incluso en ejemplos, ya que alguien podría copiar el código. Una buena parte de las preguntas en este foro se resolvería imprimiendo y leyendo la excepción dada.


You don't need to busy wait here. esp. when you expect to be waiting for such a long time. Busy waiting can make sense if you expect to be waiting a very very short amount of time. e.g. 

// From AtomicInteger 
public final int getAndSet(int newValue) { 
    for (;;) { 
     int current = get(); 
     if (compareAndSet(current, newValue)) 
      return current; 
    } 
} 

Como se puede ver, debe ser bastante raro que este bucle tiene que ir por ahí más de una vez, y de forma exponencial menos probabilidades de dar la vuelta varias veces. (En una aplicación real, en lugar de una micro-referencia) Este ciclo puede ser tan corto como 10 ns, que no es una demora larga.


Podría esperar 99 ms innecesariamente. Supongamos que el productor está agregando una entrada 1 ms más tarde, ha esperado mucho tiempo para nada.

La solución es más simple y clara.

BlockingQueue<E> queue = 

E e = queue.take(); // blocks until an element is ready. 

La lista/cola sólo se va a cambiar en otro hilo y un modelo mucho más sencillo para la gestión de hilos y colas es utilizar un ExecutorService

ExecutorService es = 

final E e = 
es.submit(new Runnable() { 
    public void run() { 
     doSomethingWith(e); 
    } 
}); 

Como se puede ver, no es necesario que trabaje con colas o subprocesos directamente. Solo necesita decir lo que quiere que haga el grupo de subprocesos.

0

No puedo agregar directamente a las respuestas excelentes dadas por David, templatetypedef, etc. - si desea evitar latencia de comunicaciones entre subprocesos y desperdicio de recursos, no haga intercomunicados con bucles sleep().

preventiva programación/despacho:

A nivel de la CPU, las interrupciones son la clave. El sistema operativo no hace nada hasta que ocurre una interrupción que hace que se ingrese su código. Tenga en cuenta que, en términos de OS, las interrupciones vienen en dos sabores: interrupciones de hardware "reales" que provocan la ejecución de controladores y "interrupciones de software". Estas son llamadas al sistema operativo de hilos que ya se están ejecutando y que pueden provocar el conjunto de subprocesos en ejecución. cambiar. Las pulsaciones de teclas, los movimientos del mouse, las tarjetas de red, los discos y las fallas de página generan interrupciones de hardware. Las funciones de espera y señal, y sleep(), pertenecen a esa segunda categoría. Cuando una interrupción de hardware hace que se ejecute un controlador, el controlador realiza la gestión de hardware para la que fue diseñado. Si el controlador necesita indicar al sistema operativo que es necesario ejecutar algún subproceso (quizás un búfer de disco esté ahora lleno y deba procesarse), el sistema operativo proporciona un mecanismo de entrada que el conductor puede llamar en lugar de ejecutar directamente una interrupción. volverse a sí mismo, (¡importante!).

Las interrupciones como las de los ejemplos anteriores pueden hacer que los subprocesos que estaban esperando estén listos para ejecutarse o que un subproceso en ejecución entre en estado de espera. Después de procesar el código de la interrupción, el sistema operativo aplica su/s algoritmo/s de planificación para decidir si el conjunto de hilos que se estaban ejecutando antes de la interrupción es el mismo que el que se debería ejecutar ahora. Si lo son, el sistema operativo simplemente interrumpe el retorno, de lo contrario, el sistema operativo debe adelantarse a uno o más de los hilos en ejecución. Si el sistema operativo necesita adelantarse a un hilo que se ejecuta en un núcleo de CPU que no es el que manejó la interrupción, tiene que obtener el control de ese núcleo de CPU. Lo hace por medio de una interrupción de hardware 'real': el controlador del interprocesador del sistema operativo establece una señal de hardware que interrumpe el núcleo que ejecuta el subproceso que se va a adelantar.

Cuando un subproceso que se va a sustituir entra en el código del sistema operativo, el sistema operativo puede guardar un contexto completo para el subproceso.Algunos de los registros ya se habrán guardado en la pila del hilo mediante la entrada de interrupción, por lo que al guardar el puntero de pila del hilo se 'guardarán' efectivamente todos esos registros, pero el sistema operativo normalmente necesitará hacer más, p.ej. las memorias caché pueden necesitar ser enjuagadas, el estado de la FPU puede necesitar ser guardado y, en el caso donde la nueva cadena a ser ejecutada pertenece a un proceso diferente al que se va a evitar, los registros de protección de administración de memoria deberán ser intercambiados . Por lo general, el sistema operativo cambia de la pila de hilos interrumpidos a una pila de SO privada lo antes posible para evitar infligir requisitos de pila de SO en cada pila de subprocesos.

Una vez que el o los contextos se guardan, el SO puede 'intercambiar' el/los contexto/s extendido/s por los nuevos hilos/s que se van a ejecutar. Ahora, el SO finalmente puede cargar el puntero de la pila para los nuevos subprocesos y realizar retornos de interrupción para ejecutar sus nuevos subprocesos listos.

El sistema operativo no hace nada en absoluto. Los subprocesos en ejecución se ejecutan hasta que se produce otra interrupción (dura o blanda).

puntos importantes:

1) El núcleo del sistema operativo debe ser considerado como una gran interrupción-controlador que puede decidir interrumpir retorno a un conjunto diferente de los hilos que las que interrumpidos.

2) El sistema operativo puede controlar y detener, si es necesario, cualquier hilo en cualquier proceso, sin importar en qué estado se encuentre o en qué núcleo se esté ejecutando.

3) La programación anticipada y el envío genera todos los problemas de sincronización que se publican en estos foros. La gran ventaja es la respuesta rápida en el nivel de hilo a las interrupciones duras. Sin esto, todas esas aplicaciones de alto rendimiento que ejecuta en su PC: transmisión de video, redes rápidas, etc., serían virtualmente imposibles.

4) El temporizador de sistema operativo es solo uno de un gran conjunto de interrupciones que pueden cambiar el conjunto de subprocesos en ejecución. 'Time-slicing', (ugh, odio ese término), entre los hilos listos solo ocurre cuando la computadora está sobrecargada, es decir. el conjunto de hilos listos es más grande que la cantidad de núcleos de CPU disponibles para ejecutarlos. Si algún texto que pretenda explicar la programación del sistema operativo menciona 'time-slicing' antes de 'interrupts', es probable que cause más confusión que explicación. La interrupción del temporizador es solo 'especial' ya que muchas llamadas al sistema tienen tiempos de espera para respaldar su función principal, (OK, para el modo reposo(), el tiempo de espera ES la función primaria :).

Cuestiones relacionadas