En ciencias de la computación e ingeniería de software , la espera activa , el bucle activo o el giro es una técnica en la que un proceso verifica repetidamente si una condición es verdadera, como si hay entrada de teclado o un bloqueo disponible. El giro también se puede usar para generar un retardo de tiempo arbitrario, una técnica que era necesaria en sistemas que carecían de un método para esperar una duración específica. Las velocidades de los procesadores varían mucho de una computadora a otra, especialmente porque algunos procesadores están diseñados para ajustar dinámicamente la velocidad en función de la carga de trabajo actual. [ 1 ] En consecuencia, el giro como técnica de retardo de tiempo puede producir resultados inconsistentes o incluso impredecibles en diferentes sistemas a menos que se incluya código para determinar el tiempo que tarda un procesador en ejecutar un bucle "no hacer nada" , o que el código del bucle verifique explícitamente un reloj en tiempo real .
En la mayoría de los casos, el uso de spinning se considera un antipatrón y debe evitarse, [ 2 ] ya que el tiempo del procesador que podría utilizarse para ejecutar una tarea diferente se desperdicia en una actividad inútil. El spinning puede ser una estrategia válida en ciertas circunstancias, sobre todo en la implementación de spinlocks dentro de sistemas operativos diseñados para ejecutarse en sistemas SMP .
Ejemplo
Los siguientes ejemplos de código Java ilustran dos hilos que comparten un enteroi global . El primer hilo utiliza espera activa para comprobar si hay un cambio en el valor de i:
paquete org.wikipedia.examples ;import java.util.concurrent.atomic.AtomicInteger ;public class Example { // Entero atómico compartido entre hilos static AtomicInteger i = new AtomicInteger ( 0 );public static void main ( String [] args ) throws InterruptedException { // Hilo f1: espera a que i cambie de 0 Thread t1 = new Thread (() -> { int local ; while (( local = i . get ()) == 0 ) { // Espera activa (bloqueo de giro) } System . out . printf ( "El valor de i ha cambiado a %d.%n" , local ); });// Hilo f2: cambia i después de 10 segundos Hilo t2 = nuevo Hilo (() -> { int local = 99 ; try { Hilo . sleep ( 10_000 ); // dormir 10 segundos } catch ( InterruptedException e ) { Hilo . currentThread (). interrupt (); return ; } i . set ( local ); System . out . printf ( "t2 ha cambiado el valor de i a %d.%n" , local ); });// Iniciar hilos t1.start ( ); t2.start ( );// Esperar a que los hilos terminen t1.join ( ); t2.join ();System.out.println ( " Todos los hilos han finalizado . " ) ; } }En un caso de uso como este, también se puede considerar el uso de variables de condición .
Alternativas
La mayoría de los sistemas operativos y bibliotecas de subprocesos proporcionan diversas llamadas al sistema que bloquean el proceso ante un evento, como la adquisición de un bloqueo, cambios de temporizador, disponibilidad de E/S o señales . El uso de estas llamadas generalmente produce el resultado más simple, eficiente, justo y libre de condiciones de carrera . Una sola llamada verifica, informa al planificador del evento que está esperando, inserta una barrera de memoria cuando corresponda y puede realizar una operación de E/S solicitada antes de regresar. Otros procesos pueden usar la CPU mientras el proceso que realiza la llamada está bloqueado. El planificador recibe la información necesaria para implementar la herencia de prioridad u otros mecanismos para evitar la inanición .
La espera activa puede optimizarse considerablemente utilizando una función de retardo (por ejemplo, `relay` sleep()) presente en la mayoría de los sistemas operativos. Esta función pone un hilo en espera durante un tiempo determinado, durante el cual no consume recursos de la CPU. Si el bucle realiza una comprobación sencilla, pasará la mayor parte del tiempo en espera y consumirá muy pocos recursos de la CPU.
Si la demora debe ser breve, también suele haber una función para simplemente ceder el procesador, entregando el control a otros procesos, pero sin esperar ningún tiempo.
En programas que nunca terminan (como los sistemas operativos), la espera activa infinita se puede implementar utilizando saltos incondicionales como se muestra en esta sintaxis NASM :
salto $La CPU saltará incondicionalmente a su propia posición para siempre. Una espera activa como esta puede reemplazarse con:
dormir: salto hlt dormirPara obtener más información, consulte HLT (instrucción x86) .
Uso apropiado
En la programación de bajo nivel, las esperas activas pueden ser deseables. Implementar el procesamiento mediante interrupciones para cada dispositivo de hardware, especialmente para aquellos que se acceden con poca frecuencia, puede no ser conveniente ni práctico. A veces es necesario escribir datos de control en el hardware y luego obtener el estado del dispositivo resultante de la operación de escritura, un estado que puede no ser válido hasta que hayan transcurrido varios ciclos de máquina. El programador podría llamar a una función de retardo del sistema operativo, pero esto podría consumir más tiempo que el que se invertiría en esperar durante unos pocos ciclos de reloj a que el dispositivo devuelva su estado.
Véase también
Referencias
Enlaces externos
- Descripción de las especificaciones básicas de The Open Group, número 6, IEEE Std 1003.1, edición de 2004.
- Artículo " Bloqueos de giro a nivel de usuario: hilos, procesos e IPC " de Gert Boddaert
- Referencia de la clase SpinLock de Austria archivada el 14 de mayo de 2011 en Wayback Machine.
- Antipatrones
- Control de concurrencia