stringtranslate.com

Ocupado esperando

En informática e ingeniería de software , la espera activa , el bucle activo o el giro es una técnica en la que un proceso comprueba repetidamente si una condición es verdadera, como si hay una entrada de teclado o un bloqueo 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 para esperar 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 en función de la carga de trabajo actual. [1] En consecuencia, el giro como técnica de retraso 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 de "no hacer nada" , o el código de bucle verifique explícitamente un reloj de tiempo real .

En la mayoría de los casos, el spinning se considera un antipatrón y se debe evitar, [2] ya que el tiempo del procesador que podría usarse para ejecutar una tarea diferente se desperdicia en una actividad inútil. El spinning puede ser una estrategia válida en determinadas circunstancias, sobre todo en la implementación de bloqueos de spin dentro de 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 subproceso utiliza la función de espera activa para comprobar si se ha producido un cambio en el valor de i :

#include <pthread.h> #include <stdatomic.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h>     /* i es global, por lo que es visible para todas las funciones. Utiliza el tipo especial * atomic_int, que permite accesos atómicos a la memoria. */ atomic_int i = 0 ;   /* f1 usa un spinlock para esperar a que i cambie de 0. */ static void * f1 ( void * p ) { int local_i ; /* Cargar atómicamente el valor actual de i en local_i y verificar si ese valor  es cero */ while (( local_i = atomic_load ( & i )) == 0 ) { /* no hacer nada - solo seguir verificando una y otra vez */ }                printf ( "el valor de i ha cambiado a %d. \n " , local_i ); devuelve NULL ; }   static void * f2 ( void * p ) { int local_i = 99 ; sleep ( 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 principal () { int rc ; pthread_t t1 , t2 ;       rc = pthread_create ( & t1 , NULL , f1 , NULL ); si ( rc != 0 ) { fprintf ( stderr , "pthread f1 falló \n " ); devolver FALLO_DE_SALIDA ; }                rc = pthread_create ( & t2 , NULL , f2 , NULL ); si ( rc != 0 ) { fprintf ( stderr , "pthread f2 falló \n " ); devolver ERROR_SALIDA ; }                pthread_join ( t1 , NULL ); pthread_join ( t2 , NULL ); puts ( "Todos los pthreads finalizaron." ); return 0 ; }      

En un caso de uso como este, se puede considerar utilizar las variables de condición de C11 .

Alternativas

La mayoría de los sistemas operativos y bibliotecas de subprocesos proporcionan una variedad de llamadas al sistema que bloquearán el proceso en un evento, como adquisición de bloqueo, cambios de temporizador, disponibilidad de E/S o señales . El uso de dichas llamadas generalmente produce el resultado más simple, eficiente, justo y sin carreras . Una sola llamada verifica, informa al programador 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 llamador está bloqueado. El programador recibe la información necesaria para implementar la herencia de prioridad u otros mecanismos para evitar la inanición .

La espera activa puede resultar mucho menos derrochadora si se utiliza una función de retardo (por ejemplo, sleep()) que se encuentra en la mayoría de los sistemas operativos. Esta función pone un hilo a dormir durante un tiempo específico, durante el cual el hilo no desperdiciará tiempo de CPU. Si el bucle está verificando algo simple, pasará la mayor parte del tiempo dormido y desperdiciará muy poco tiempo de CPU.

En programas que nunca terminan (como los sistemas operativos), se puede implementar una espera activa infinita mediante saltos incondicionales, como se muestra en esta sintaxis NASMjmp $ : . La CPU saltará incondicionalmente a su propia posición para siempre. Una espera activa 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 activas pueden ser realmente deseables. Puede que no sea deseable ni práctico implementar un procesamiento controlado por interrupciones para cada dispositivo de hardware, en particular aquellos a los que se accede con poca frecuencia. 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 volverse válido hasta que hayan transcurrido varios ciclos de máquina después de la escritura. El programador podría llamar a una función de retardo del sistema operativo, pero al hacerlo puede consumir más tiempo del que se gastaría en esperar unos pocos ciclos de reloj a que el dispositivo devuelva su estado.

Véase también

Referencias

  1. ^ "Tecnología Intel Turbo Boost".
  2. ^ "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