В компьютерной науке и программной инженерии активное ожидание , активное зацикливание или вращение — это метод, при котором процесс многократно проверяет, выполняется ли условие, например, доступен ли ввод с клавиатуры или блокировка . Вращение также может использоваться для создания произвольной задержки по времени, метода, который был необходим в системах, в которых отсутствовал метод ожидания определенного периода времени. Скорости процессоров сильно различаются от компьютера к компьютеру, особенно потому, что некоторые процессоры разработаны для динамической регулировки скорости в зависимости от текущей рабочей нагрузки. [1] Следовательно, вращение как метод задержки по времени может давать непоследовательные или даже непредсказуемые результаты на разных системах, если только не включен код для определения времени, необходимого процессору для выполнения цикла «ничего не делать» , или код цикла явно не проверяет часы реального времени .
В большинстве случаев спиннинг считается антишаблоном и его следует избегать, [2] поскольку процессорное время, которое можно было бы использовать для выполнения другой задачи , вместо этого тратится на бесполезную деятельность. Спайннинг может быть допустимой стратегией в определенных обстоятельствах, особенно при реализации спин-блокировок в операционных системах, разработанных для работы на системах SMP .
Следующие примеры кода C иллюстрируют два потока, которые совместно используют глобальное целое число i . Первый поток использует активное ожидание для проверки изменения значения i :
#include <pthread.h> #include <stdatomic.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> /* i является глобальным, поэтому он виден всем функциям. Он использует специальный * тип atomic_int, который позволяет осуществлять атомарный доступ к памяти. */ atomic_int i = 0 ; /* f1 использует спин-блокировку для ожидания изменения i с 0. */ static void * f1 ( void * p ) { int local_i ; /* Атомарно загружает текущее значение i в local_i и проверяет, равно ли это значение нулю */ while (( local_i = atomic_load ( & i )) == 0 ) { /* ничего не делает — просто продолжает проверять снова и снова */ } printf ( "значение i изменилось на %d. \n " , local_i ); return NULL ; } static void * f2 ( void * p ) { int local_i = 99 ; sleep ( 10 ); /* сон на 10 секунд */ atomic_store ( & i , local_i ); printf ( "t2 изменил значение i на %d. \n " , local_i ); return NULL ; } int main () { int rc ; pthread_t t1 , t2 ; rc = pthread_create ( & t1 , NULL , f1 , NULL ); если ( rc != 0 ) { fprintf ( stderr , "pthread f1 не удался \n " ); return EXIT_FAILURE ; } rc = pthread_create ( & t2 , NULL , f2 , NULL ); если ( rc != 0 ) { fprintf ( stderr , "pthread f2 не удался \n " ); return EXIT_FAILURE ; } pthread_join ( t1 , NULL ); pthread_join ( t2 , NULL ); puts ( "Все потоки pthreads завершены." ); return 0 ; }
В таком случае можно рассмотреть возможность использования условных переменных C11 .
Большинство операционных систем и потоковых библиотек предоставляют множество системных вызовов , которые блокируют процесс при возникновении события, например, при получении блокировки, изменении таймера, доступности ввода-вывода или сигналах . Использование таких вызовов обычно дает самый простой, эффективный, справедливый и свободный от гонок результат. Одиночный вызов проверяет, информирует планировщик о событии, которого он ожидает, вставляет барьер памяти , где это применимо, и может выполнить запрошенную операцию ввода-вывода перед возвратом. Другие процессы могут использовать ЦП, пока вызывающий заблокирован. Планировщику предоставляется информация, необходимая для реализации наследования приоритетов или других механизмов, чтобы избежать голодания .
Само активное ожидание можно сделать гораздо менее расточительным, используя функцию задержки (например, sleep()
), имеющуюся в большинстве операционных систем. Это переводит поток в спящий режим на определенное время, в течение которого поток не будет тратить процессорное время. Если цикл проверяет что-то простое, то большую часть времени он будет проводить в спящем режиме и будет тратить очень мало процессорного времени.
В программах, которые никогда не заканчиваются (например, операционные системы), бесконечное активное ожидание может быть реализовано с помощью безусловных переходов, как показано в этом синтаксисе NASMjmp $ : . Процессор будет безусловно переходить на свою собственную позицию навсегда. Такое активное ожидание можно заменить на:
сон: hlt jmp сон
Для получения дополнительной информации см. HLT (инструкция x86) .
В низкоуровневом программировании активное ожидание может быть на самом деле желательным. Может быть нежелательно или непрактично реализовывать обработку, управляемую прерываниями, для каждого аппаратного устройства, особенно тех, к которым редко обращаются. Иногда необходимо записать какие-то управляющие данные на оборудование, а затем получить статус устройства, полученный в результате операции записи, статус, который может не стать действительным, пока не пройдет несколько машинных циклов после записи. Программист может вызвать функцию задержки операционной системы, но это может занять больше времени, чем было бы потрачено на вращение в течение нескольких тактов в ожидании возврата устройством своего статуса.