En ciencias de la computación y la ingeniería de software , ocupado en espera , ocupado-bucle o hilado es una técnica en la que un proceso repetidamente comprobaciones para ver si una condición es verdadera, como por ejemplo si el teclado de entrada o una cerradura está disponible. El giro también se puede utilizar para generar un retraso de tiempo arbitrario, una técnica que era necesaria en sistemas que carecían de un método de espera durante un período de tiempo específico. 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 según la carga de trabajo actual [1]. En consecuencia, girar como una técnica de retardo de tiempo puede producir resultados impredecibles o incluso inconsistentes en diferentes sistemas, a menos que se incluya un código para determinar el tiempo que tarda un procesador en ejecutar un bucle de "no hacer nada" , o que el código de bucle verifique explícitamente un reloj en tiempo real .
En la mayoría de los casos, el giro se considera un antipatrón y debe evitarse, [2] ya que el tiempo del procesador que podría usarse para ejecutar una tarea diferente se desperdicia en actividades inútiles. Spinning puede ser una estrategia válida en determinadas circunstancias, sobre todo en la implementación de spinlocks dentro de los sistemas operativos diseñados para ejecutarse en sistemas SMP .
Ejemplo de código C
Los siguientes ejemplos de código C ilustran dos subprocesos que comparten un entero global i . El primer hilo usa la espera ocupada para verificar un cambio en el valor de i :
#include #include #include #include #include / * i es global, por lo que es visible para todas las funciones. Hace uso del tipo especial * atomic_int, que permite accesos a memoria atómica. * / atomic_int i = 0 ;/ * f1 usa un spinlock para esperar a que i cambie de 0. * / static void * f1 ( void * p ) { int local_i ; / * Cargue atómicamente el valor actual de i en local_i y verifique si ese valor es cero * / while (( local_i = atomic_load ( & i )) == 0 ) { / * no haga nada, solo siga revisando una y otra vez * / } printf ( "el valor de i ha cambiado a% d. \ n " , local_i ); return NULL ; } vacío estático * f2 ( vacío * p ) { int local_i = 99 ; dormir ( 10 ); / * dormir durante 10 segundos * / atomic_store ( & i , local_i ); printf ( "t2 ha cambiado el valor de i a% d. \ n " , local_i ); return NULL ; }int main () { int rc ; pthread_t t1 , t2 ; rc = pthread_create ( & t1 , NULL , f1 , NULL ); if ( rc ! = 0 ) { fprintf ( stderr , "pthread f1 falló \ n " ); return EXIT_FAILURE ; } rc = pthread_create ( & t2 , NULL , f2 , NULL ); if ( rc ! = 0 ) { fprintf ( stderr , "pthread f2 falló \ n " ); return EXIT_FAILURE ; } pthread_join ( t1 , NULL ); pthread_join ( t2 , NULL ); put ( "Todos los subprocesos terminados" ); return 0 ; }
En un caso de uso como este, se puede considerar el uso de las variables de condición de C11 .
Alternativas
La mayoría de los sistemas operativos y las bibliotecas de subprocesos proporcionan una variedad de llamadas al sistema que bloquearán el proceso en un evento, como la adquisición de bloqueos, cambios de temporizador, disponibilidad de E / S o señales . El uso de tales llamadas generalmente produce el resultado más simple, más eficiente, justo y sin carreras . Una sola llamada verifica, informa al programador del evento que está esperando, inserta una barrera de memoria donde corresponda y puede realizar una operación de E / S solicitada antes de regresar. Otros procesos pueden usar la CPU mientras la persona que llama está bloqueada. El planificador recibe la información necesaria para implementar la herencia prioritaria u otros mecanismos para evitar la inanición .
La espera ocupada en sí se puede hacer mucho menos derrochadora mediante el uso de una función de retraso (por ejemplo, sleep()
) que se encuentra en la mayoría de los sistemas operativos. Esto pone un hilo en suspensión durante un tiempo específico, durante el cual el hilo no desperdiciará tiempo de CPU. Si el bucle está comprobando algo simple, pasará la mayor parte del tiempo dormido y perderá muy poco tiempo de CPU.
En los programas que nunca terminan (por ejemplo, sistemas operativos), infinita espera ocupada se puede implementar mediante el uso de saltos incondicionales como se muestra por esta NASM sintaxis: jmp $. La CPU saltará incondicionalmente a su propia posición para siempre. Una espera ocupada como esta se puede reemplazar con:
dormir: hlt jmp dormir
Para obtener más información, consulte HLT (instrucción x86) .
Uso apropiado
En la programación de bajo nivel, las esperas ocupadas pueden ser deseables. Puede que no sea deseable o práctico implementar el procesamiento controlado por interrupciones para todos los dispositivos de hardware, particularmente aquellos a los que rara vez se accede. A veces es necesario escribir algún tipo de datos de control en el hardware y luego obtener el estado del dispositivo resultante de la operación de escritura, estado que puede no ser válido hasta que hayan transcurrido varios ciclos de la máquina después de la escritura. El programador podría llamar a una función de retardo del sistema operativo, pero hacerlo puede consumir más tiempo del que se gastaría girando durante unos pocos ciclos de reloj esperando que el dispositivo vuelva a su estado.
Ver también
Referencias
- ^ "Tecnología Intel Turbo Boost" .
- ^ "Por qué no se debe utilizar la clase de tipo 'volátil'" . Archivado desde el original el 4 de octubre de 2017 . Consultado el 10 de junio de 2013 .
enlaces externos
- Descripción de The Open Group Base Especificaciones Edición 6, IEEE Std 1003.1, Edición 2004
- Artículo " Bloqueos de giro a nivel de usuario: subprocesos, procesos e IPC " por Gert Boddaert
- Referencia de clase SpinLock de Austria