Thread::yield vs Thread::onSpinWait

Ну, название в основном говорит само за себя, с небольшим дополнением, что я действительно хотел бы знать, когда их использовать. И это может быть достаточно просто - я читал документацию к ним обоим, но до сих пор не вижу большой разницы.

Здесь есть такие ответы, как это, которые в основном говорят:

Yielding also was useful for busy waiting...

Я не могу с ними согласиться по той простой причине, что ForkJoinPool использует Thread::yield внутри, а это довольно недавнее дополнение в мире jdk.

Что меня действительно беспокоит, так это использование в jdk (StampledLock::tryDecReaderOverflow):

    else if ((LockSupport.nextSecondarySeed() & OVERFLOW_YIELD_RATE) == 0)
        Thread.yield();
    else
        Thread.onSpinWait();
    return 0L;

Так что, кажется, есть случаи, когда одно было бы предпочтительнее другого. И нет, у меня нет фактического примера, где мне это может понадобиться - единственный, который я действительно использовал, был Thread::onSpinWait, потому что 1) я был занят ожиданием 2) имя в значительной степени говорит само за себя, что я должен использовал его в занятый спин.

Можете ли вы описать сценарий, в котором вам нужно было бы ждать вращения? Мне, как пользователю Java, а не разработчику, это никогда не требовалось. Конечно, в программировании ядра спин-блокировки используются повсеместно. А на Яве? Я не могу придумать вариант использования, в котором я бы крутил ожидание, а не использовал блокировку, монитор или асинхронный обратный вызов. (Я не шучу. Спрашивать, какой из них использовать, возможно, неправильный вопрос, если ответ таков: вряд ли кому-то следует использовать любой из них.)

John Kugelman 09.05.2019 12:00

Возможный дубликат Метод onSpinWait() класса Thread

Torben 09.05.2019 12:00

@JohnKugelman Я никогда не нужный - я просто знал, что этот метод существует, и в его документации есть пример, который очень близко соответствует тому, что у меня был, поэтому решил использовать его, в надежде, что он каким-то образом улучшит производительность. Я также никогда не нуждался в активном вращении, во-первых, это было похоже на то, что я должен был обязательно попробовать.

Eugene 09.05.2019 12:05

@Torben, учитывая, что я дал один из ответов на этот вопрос, я не думаю, что это дубликат.

Eugene 09.05.2019 12:05

В примере с API показан цикл, вращающийся с флагом volatile. Это нормально, если вы реализуете низкоуровневый примитив блокировки. Скорее всего, большинство людей, читающих это, не являются. Им следует держаться подальше от такой идиомы. Вращение по переменной предназначено только для полных экспертов и полных новичков.

John Kugelman 09.05.2019 12:11

@JohnKugelman хм .. Я был бы во второй группе, просто мне показалось, что я должен был попробовать, и это не так уж сложно, если все, что делает занятое вращение, действует как монитор.

Eugene 09.05.2019 12:18
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
11
6
849
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

При блокировке темы есть несколько стратегий на выбор: вращение, wait()/notify() или их комбинация. Чистое вращение на переменной — это стратегия с очень малой задержкой, но она может привести к голоданию других потоков, которые борются за процессорное время. С другой стороны, wait() / notify() высвобождает ЦП для других потоков, но может стоить тысячи циклов ЦП из-за задержки при удалении/планировании потоков.

Итак, как мы можем избежать чистого вращения, а также накладных расходов, связанных с депланированием и планированием заблокированного потока?

Thread.yield() — это подсказка планировщику потоков отказаться от своего кванта времени, если готов другой поток с таким же или более высоким приоритетом. Это позволяет избежать чистого вращения, но не позволяет избежать накладных расходов на повторное планирование потока.

Последним дополнением является Thread.onSpinWait(), который вставляет специфичные для архитектуры инструкции, чтобы намекнуть процессору, что поток находится в цикле вращения. На x86 это, наверное, инструкция PAUSE, на aarch64 это инструкция YIELD.

Какая польза от этих инструкций? В чистом цикле вращения процессор будет спекулятивно выполнять цикл снова и снова, заполняя конвейер. Когда переменная, на которой вращается поток, окончательно изменится, вся эта спекулятивная работа будет отброшена из-за нарушения порядка памяти. Какая трата!

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

Таким образом, onSpinWait() — это правильно, если мы ожидаем, что другой поток уже работает на другом процессоре (ядре), выполняющем условие, тогда как yield() — это правильно, если мы ожидаем, что у другого потока нет процессорного времени. К сожалению, мы не можем этого знать, поэтому пример кода, показанный в вопросе, использует некоторую случайную эвристику, чтобы решить, когда какой метод вызывать.

Holger 09.05.2019 18:38

Другие вопросы по теме