VarHandle get/setOpaque

Я продолжаю бороться, чтобы понять, что VarHandle::setOpaque а также VarHandle::getOpaque действительно делают. Пока это было нелегко - есть некоторые вещи, которые, я думаю, я получаю (но не буду представлять их в самом вопросе, а не в мутной воде), но в целом это в лучшем случае мисс лидирует для меня.

Документация:

Возвращает значение переменной, доступ к которой осуществляется в программном порядке...

Ну, в моем понимании, если у меня есть:

int xx = x; // read x
int yy = y; // read y

Эти чтения могут быть переупорядочены. С другой стороны, если у меня есть:

// simplified code, does not compile, but reads happen on the same "this" for example
int xx = VarHandle_X.getOpaque(x); 
int yy = VarHandle_Y.getOpaque(y);

На этот раз повторные заказы невозможны? И это что значит "программный заказ"? Мы говорим о вставке барьеров здесь для того, чтобы этот повторный заказ был запрещен? Если это так, поскольку это две нагрузки, будет ли достигнуто то же самое? с помощью:

 int xx = x;
 VarHandle.loadLoadFence()
 int yy = y;

Но это становится намного сложнее:

... но без гарантии эффектов упорядочения памяти по отношению к другим потокам.

Я не мог придумать пример, чтобы притвориться, что я понимаю эту часть.

Мне кажется, что эта документация предназначена для людей, которые точно знают, что они делают (и я определенно не один)... Так может кто-то пролить свет на это?

3 ответа

Ну, в моем понимании, если у меня есть:

int xx = x; // read x
int yy = y; // read y

Эти чтения могут быть переупорядочены.

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

С другой стороны, если у меня есть:

// simplified code, does not compile, but reads happen on the same "this" for example
int xx = VarHandle_X.getOpaque(x); 
int yy = VarHandle_Y.getOpaque(y);

На этот раз повторные заказы невозможны? И это что значит "программный заказ"?

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

Термин программный порядок определяется JLS:

Программный порядок t является полным порядком, который отражает порядок, в котором эти действия будут выполняться в соответствии с семантикой внутри потока t.

Это порядок оценки, указанный для выражений и операторов. Порядок, в котором мы воспринимаем эффекты, если только один поток вовлечен.

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

Нет, в этом нет никакого препятствия, которое может заключаться в фразе " … но без гарантии эффектов упорядочения памяти по отношению к другим потокам ".

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

Так что вы можете сделать с этим?

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

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

Это также полезно, если вы просто хотите атомарный доступ для long а также double без какого-либо другого воздействия.

Поскольку действительно неизменяемые объекты используют final поля нечувствительны к гонкам данных, вы можете использовать непрозрачные режимы для своевременной публикации неизменяемых объектов без более широкого эффекта публикации в режиме выпуска / получения.

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

Я сам боролся с непрозрачностью, и документацию, безусловно, нелегко понять.

Из приведенной выше ссылки:

Непрозрачные операции являются побитовыми атомарными и когерентно упорядоченными.

Побитовая атомарная часть очевидна. Согласованно упорядоченный означает, что загрузки/сохранения по одному адресу имеют некоторый общий порядок, каждый охват видит самый последний адрес перед ним, и порядок соответствует порядку программы. Некоторые примеры согласованности см. в следующем тесте JCStress.

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

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

Я обновил ваш пример до лакмусовой бумажки передачи сообщений:

      thread1:
X.setOpaque(1);
Y.setOpaque(1);

thread2:
ry = Y.getOpaque();
rx = X.getOpaque();
if (ry == 1 && rx == 0) println("Oh shit");

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

Кроме того, следующий пример IRIW может дать сбой:

      thread1:
X.setOpaque(1);

thread2:
Y.setOpaque(1);

thread3:
rx_thread3 = X.getOpaque();
[LoadLoad]
ry_thread3 = Y.getOpaque();

thread4:
ry_thread4 = Y.getOpaque();
[LoadLoad]
rx_thread4 = X.getOpaque();

Может ли быть так, что мы получим rx_thread3=1,ry_thread3=0,ry_thread4=1, а rx_thread4 равен 0?

С непрозрачным такое может случиться. Несмотря на то, что переупорядочивание загрузки запрещено, непрозрачный доступ не требует атомарности множественного копирования (сохранения по разным адресам, выданным разными ЦП, можно увидеть в разном порядке).

Освобождение/приобретение более сильное, чем непрозрачное, поскольку при освобождении/приобретении допускается сбой, поэтому с непрозрачным допускается сбой. Таким образом, Opaque не требуется для обеспечения консенсуса.

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

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

запись упорядочена с пересылкой буфера хранилища

модель памяти, поэтому, даже если поток сохраняет перед загрузкой. Хранилище можно кэшировать в буфере хранилища, и некоторый поток, выполняемый на любом другом ядре, наблюдает за действием потока в обратном порядке загрузка-хранилище, а не хранилище-загрузка. Таким образом, непрозрачная операция выполняется на x86 бесплатно (на x86 мы фактически также можем получить бесплатно, см. Этот чрезвычайно исчерпывающий ответ для получения подробной информации о некоторых других архитектурах и их моделях памяти: /questions/48977660/poryadok-pamyati-potreblyaet-ispolzovanie-v-c11/48977683#48977683)

Почему это полезно? Что ж, я мог бы предположить, что если какой-то поток наблюдал значение, сохраненное с семантикой непрозрачной памяти, то при последующем чтении будет наблюдаться значение "по крайней мере это или более позднее" (простой доступ к памяти не дает таких гарантий, не так ли?).

Кроме того, поскольку Java 9 VarHandles в некоторой степени связаны с семантикой получения / выпуска / потребления в C, я думаю, что стоит отметить, что непрозрачный доступ похож на memory_order_relaxed который определяется в Стандарте следующим образом:

За memory_order_relaxed, никакая операция не заказывает память.

с некоторыми примерами.

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