Занято ожиданием
В информатике и обеспечения разработке программного ожидание занятости , циклическое воспроизведение или вращение — это метод, при котором процесс неоднократно проверяет, истинно ли условие, например, ли ввод с клавиатуры или блокировка доступен . Вращение также можно использовать для создания произвольной временной задержки - метод, который был необходим в системах, в которых не было метода ожидания определенного периода времени. Скорость процессора сильно различается от компьютера к компьютеру, особенно потому, что некоторые процессоры предназначены для динамической регулировки скорости в зависимости от текущей рабочей нагрузки. [1] Следовательно, вращение как метод задержки может привести к непредсказуемым или даже противоречивым результатам в разных системах, если не включен код для определения времени, которое процессору требуется для выполнения цикла «ничего не делать» , или код цикла явно проверяет часы реального времени. .
В большинстве случаев вращение считается антипаттерном , и его следует избегать. [2] поскольку время процессора, которое можно было бы использовать для выполнения другой задачи, вместо этого тратится на бесполезную деятельность. Вращение может быть действенной стратегией в определенных обстоятельствах, особенно при реализации спин-блокировок в операционных системах, предназначенных для работы в системах SMP .
Пример кода C
[ редактировать ]Следующие примеры кода C иллюстрируют два потока, которые совместно используют глобальное целое число i . Первый поток использует ожидание занятости, чтобы проверить изменение значения i :
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* i is global, so it is visible to all functions. It makes use of the special
* type atomic_int, which allows atomic memory accesses.
*/
atomic_int i = 0;
/* f1 uses a spinlock to wait for i to change from 0. */
static void *f1(void *p)
{
int local_i;
/* Atomically load current value of i into local_i and check if that value
is zero */
while ((local_i = atomic_load(&i)) == 0) {
/* do nothing - just keep checking over and over */
}
printf("i's value has changed to %d.\n", local_i);
return NULL;
}
static void *f2(void *p)
{
int local_i = 99;
sleep(10); /* sleep for 10 seconds */
atomic_store(&i, local_i);
printf("t2 has changed the value of i to %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 failed\n");
return EXIT_FAILURE;
}
rc = pthread_create(&t2, NULL, f2, NULL);
if (rc != 0) {
fprintf(stderr, "pthread f2 failed\n");
return EXIT_FAILURE;
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
puts("All pthreads finished.");
return 0;
}
В таком случае использования можно рассмотреть возможность использования C11 состояния переменных .
Альтернативы
[ редактировать ]Большинство операционных систем и библиотек потоков предоставляют различные системные вызовы , которые блокируют процесс по событию, например получение блокировки, изменение таймера, доступность ввода-вывода или сигналы . Использование таких вызовов обычно дает самый простой, наиболее эффективный, справедливый и свободный от гонок результат. Одиночный вызов проверяет, информирует планировщик об ожидаемом событии, вставляет барьер памяти , где это применимо, и может выполнить запрошенную операцию ввода-вывода перед возвратом. Другие процессы могут использовать ЦП, пока вызывающий объект заблокирован. Планировщику предоставляется информация, необходимая для реализации наследования приоритетов или других механизмов, позволяющих избежать истощения .
Само ожидание при занятости можно сделать гораздо менее расточительным, если использовать функцию задержки (например, sleep()
) встречается в большинстве операционных систем. Это переводит поток в спящий режим на определенное время, в течение которого поток не будет тратить время процессора. Если цикл проверяет что-то простое, то большую часть времени он будет спать и тратить очень мало процессорного времени.
В программах, которые никогда не заканчиваются (например, в операционных системах), бесконечное ожидание занятости может быть реализовано с помощью безусловных переходов, как показано в этом синтаксисе NASM : jmp $. ЦП безоговорочно перейдет на свою позицию навсегда. Такое напряженное ожидание можно заменить следующим:
sleep:
hlt
jmp sleep
Дополнительную информацию см. в разделе HLT (инструкция x86) .
Надлежащее использование
[ редактировать ]В низкоуровневом программировании ожидание занятости действительно может быть желательным. Возможно, нежелательно и непрактично реализовывать обработку, управляемую прерываниями, для каждого аппаратного устройства, особенно для тех, к которым редко обращаются. Иногда необходимо записать какие-то управляющие данные в аппаратное обеспечение, а затем получить статус устройства, полученный в результате операции записи, статус, который может стать недействительным до тех пор, пока после записи не пройдет несколько машинных циклов. Программист может вызвать функцию задержки операционной системы, но это может занять больше времени, чем было бы затрачено на несколько тактовых циклов ожидания, пока устройство вернет свое состояние.
См. также
[ редактировать ]- Опрос (информатика)
- Неблокирующий ввод-вывод
- Спинлок
- БогоМипс
- изменчивая переменная
- Синхронизация (информатика)
- Алгоритм Петерсона
Ссылки
[ редактировать ]- ^ «Технология Intel Turbo Boost» .
- ^ «Почему не следует использовать класс типа «летучий»» . Архивировано из оригинала 4 октября 2017 г. Проверено 10 июня 2013 г.
Внешние ссылки
[ редактировать ]- Описание из базовых спецификаций открытой группы, выпуск 6, IEEE Std 1003.1, издание 2004 г.
- Статья « Спин-блокировки на уровне пользователя — потоки, процессы и IPC ». Герта Боддата
- Справочник класса SpinLock в Австрии. Архивировано 14 мая 2011 г. на Wayback Machine.