Холостой цикл

Холостой цикл (также «холостое ожидание», англ. busy waiting) — реализация ожидания в компьютерной программе, в котором проверка определённого условия осуществляется в бесконечном цикле. Выход из бесконечного цикла происходит только при удовлетворении проверяемого условия.

Также холостой цикл может использоваться для создания произвольной задержки выполнения программы.

В большинстве случаев холостой цикл считается антипаттерном, которого нужно избегать путём реорганизации кода или использование иного подхода к разработке (асинхронное выполнение, событийно-ориентированное программирование и т. п.).

Пример реализации на Си

править

Во фрагменте кода ниже один из потоков ожидает значения 0 в переменной 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 не изменит значение на ненулевое. */
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);
    if (rc != 0) {
        fprintf(stderr, "Ошибка в pthread f1\n");
        return EXIT_FAILURE;
    }

    rc = pthread_create(&t2, NULL, f2, NULL);
    if (rc != 0) {
        fprintf(stderr, "Ошибка в f2 failed\n");
        return EXIT_FAILURE;
    }

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    puts("Все pthread завершились.");
    return 0;
}

Примеры реализации на Java

править

Данная реализации использует обращение к методу Thread.sleep() в цикле, что позволяет приостановить исполнение потока на заданное количество миллисекунд:

long delay = 1L; // время в миллескундах

volatile boolean waitForEvent = true; // значение выставляется из других потоков

while (waitForEvent) {
  Thread.sleep(delay);
}

При этом планировщик отдаёт вычислительные ресурсы другим потокам, из-за чего «усыпление» и «побудка» потока являются дорогостоящими операциями. Другим недостатком данного способа является необходимость обработки исключения, а также невозможность приостановить поток менее чем на 1 миллисекунду. Начиная с Java 9 появился метод Thread.onSpinWait(), который позволяет реализовать непродолжительное ожидание без приостановки потока:

volatile boolean waitForEvent = true; // значение выставляется из других потоков

while (waitForEvent) {
  Thread.onSpinWait();
}

Преимуществом данного подхода является возможность мгновенно прервать ожидание и продолжить выполнение.

Низкоуровневое применение

править

Одним из подвидов холостого ожидания является спин-блокировка.

В низкоуровневом программировании холостые циклы находят более широкое применение. На практике прерывание не всегда желательно для некоторых аппаратных устройств. Например, при необходимости записи некоторой контрольной информации в устройство и получения отклика об итогах записи разработчик может обратится к функции задержки на уровне ОС, однако её вызов может потребовать больше времени, поэтому используется цикл активного ожидания.

См. также

править