Согласованность памяти после завершения рабочих потоков Java в параллельном потоке - PullRequest
2 голосов
/ 21 февраля 2020

Учитывая следующий код:

final int n = 50;
final int[] addOne = new int[n];
IntStream.range(0, n)
        .parallel()
        .forEach(i -> addOne[i] = i + 1);
// (*) Are the addOne[i] values all visible here?
for (int value : addOne) {
    System.out.println(value);
}

Вопрос: После завершения рабочих потоков (т. Е. В точке (*)), можно ли гарантировать, что основной поток будет увидеть все содержимое массива, написанное рабочими потоками?

Мне интересно понять, что говорит модель памяти Java по поводу вышеуказанного вопроса. Это не имеет ничего общего с проблемами параллелизма как таковыми (т.е. с тем фактом, что параллельные потоки в Java могут обрабатывать свои элементы в любом порядке). Чтобы исключить некоторые ответы, я знаю, что невозможно гарантировать семантику упорядочения памяти между двумя разными потоками с доступом к одному и тому же элементу массива в Java без использования чего-то вроде AtomicReferenceArray<E>. Для целей этого вопроса предположим, что Atomic* классы не будут использоваться параллельными работниками. Что еще более важно, обратите внимание, что никакие два рабочих потока никогда не пытаются писать в один и тот же элемент массива, поскольку все значения i являются уникальными. Поэтому семантика упорядочения памяти между потоками здесь не важна, только будет ли любое значение, записанное в элемент массива рабочим потоком, всегда видимым для основного потока после завершения параллельного потока.

Существует вычислительный «барьер» между инициализацией элементов массива в главном потоке и запуском параллельных рабочих потоков (рабочие всегда первоначально увидят элементы с нулевым значением инициализатора). И существует барьер завершения, который ожидает завершения всех рабочих в конце потока, прежде чем передать управление обратно в основной поток. Таким образом, на самом деле вопрос сводится к , можно ли предположить полное упорядочение или неявный «барьер памяти» sh, когда на конец параллельного потока накладывается вычислительный барьер .

Спрашивается Иначе, есть ли вероятность, что основной поток может прочитать значение инициализации по умолчанию 0 для некоторого элемента после точки (*)? Или иерархия кэша ЦП всегда гарантирует, что основной поток увидит самое последнее значение, записанное в массив рабочим потоком, даже если это значение еще не было выгружено из кэша ЦП обратно в ОЗУ?

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

Ответы [ 2 ]

0 голосов
/ 24 февраля 2020

Отвеченный ответ:

Пул Fork-Join, в котором выполнение конвейера после parallel() имеет fork() invoke() и join() шагов и последний шаг в этой последовательности join() семантически эквивалентен Thread.join(), что означает, что существует семантика случайных до * между параллельной задачей, выполняемой пулом Fork-Join, и оператором после него.

0 голосов
/ 21 февраля 2020

JMM говорит:

Все поля экземпляров, поля stati c и элементы массива хранятся в памяти кучи. В этой главе мы используем термин «переменная» для ссылки как на поля, так и на элементы массива

Это означает, что вам необходимо убедиться, что между записью и чтением элементов массива существует связь «до и после».

Javado c метода java.util.stream.IntStream#forEach говорит:

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

Это означает, что вы должны принудительно установить отношение «до того» между записью и чтением элементов массива, поэтому нет никакой гарантии, что основной поток увидит все содержимое массива, записанное рабочими потоками.

PS: Streams - это сложная структура, и, на самом деле, я не уверен, действительно ли это небезопасно в вашей конкретной ситуации, но сжимайтесь говорит, что нет гарантии, если вы обращаетесь к общему состоянию (ваш массив распределяется между вызывающим потоком и работниками), и лучше следовать контракту.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...