На рис. 5.6 показаны отношения происходит-раньше, имеющие место в программе из листинга 5.7, а также возможный исход, когда два потока-читателя имеют разное представление о мире. Это возможно, потому что, как уже было сказано, не существует отношения происходит-раньше, которое вводило бы упорядочение.
Рис. 5.6.Захват-освобождение и отношения происходит-раньше
Чтобы осознать преимущества упорядочения захват-освобождение, нужно рассмотреть две операции сохранения в одном потоке, как в листинге 5.5. Если при сохранении y
задать семантику memory_order_release
, а при загрузке y
— семантику memory_order_acquire
, как в листинге ниже, то операции над x
станут упорядоченными.
Листинг 5.8.Операции с семантикой захвата-освобождения могут упорядочить ослабленные операции
#include
#include
#include
std::atomic x, y;
std::atomic z;
void write_x_then_y() {
x.store(true,std::memory_order_relaxed); ←
(1)
y.store(true,std::memory_order_release); ←
(2)
}
void read_y_then_x() {
while (!y.load(std::memory_order_acquire));←
(3)
if (x.load(std::memory_order_relaxed)) ←
(4)
++z;
}
int main() {
x = false;
y = false;
z = 0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load() != 0); ←
(5)
}
В конечном итоге операция загрузки y
(3)увидит значение true
, записанное операцией сохранения (2). Поскольку сохранение производится в режиме memory_order_release
, а загрузка — в режиме memory_order_acquire
, то сохранение синхронизируется-с загрузкой. Сохранение x
(1)происходит-раньше сохранения y
(2), потому что обе операции выполняются в одном потоке. Поскольку сохранение y
синхронизируется-с загрузкой y
, то сохранение x
также происходит-раньше загрузки y
, и, следовательно, происходит-раньше загрузки x
(4). Таким образом, операция загрузки x
должна прочитать true
, и, значит, утверждение (5) не может сработать. Если бы загрузка y
не повторялась в цикле while
, то высказанное утверждение могло бы оказаться неверным; операция загрузки y
могла бы прочитать false
, и тогда не было бы никаких ограничений на значение, прочитанное из x
. Для обеспечения синхронизации операции захвата и освобождения должны употребляться парами. Значение, сохраненное операций восстановления, должно быть видно операции захвата, иначе ни та, ни другая не возымеют эффекта. Если бы сохранение в предложении (2)или загрузка в предложении (3)выполнялись в ослабленной операции, то обращения к x
не были бы упорядочены, и, значит, нельзя было бы гарантировать, что операция загрузки в предложении (4)прочитает значение true
, поэтому утверждение assert
могло бы сработать.
К упорядочению захват-освобождение можно применить метафору человека с блокнотом в боксе, если ее немного дополнить. Во-первых, допустим, что каждое сохранение является частью некоторого пакета обновлений, поэтому, обращаясь к человеку с просьбой записать число, вы заодно сообщается ему идентификатор пакета, например: «Запиши 99 как часть пакета 423». Если речь идет о последнем сохранении в пакете, то мы сообщаем об этом: «Запиши 147, отметив, что это последнее сохранение в пакете 423». Человек в боксе честно записывает эту информацию вместе с указанным вами значением. Так моделируется операция сохранения с освобождением. Когда вы в следующий раз попросите записать значение, помер пакета нужно будет увеличить: «Запиши 41 как часть пакета 424».
Теперь, когда вы просите сообщить значение, у вас есть выбор: узнать только значение (это аналог ослабленной загрузки) или значение и сведения о том, является ли оно последним в пакете (это аналог загрузки с захватом). Если информация о пакете запрашивается, по значение не последнее в пакете, то человек ответит: «Число равно 987, и это 'обычное' значение»; если же значение последнее, то ответ прозвучит так: «Число 987, последнее в пакете 956 от Анны». Тут-то и проявляется семантика захвата-освобождения: если, запрашивая значение, вы сообщите человеку номера всех пакетов, о которых знаете, то он найдёт в своем списке последнее значение из всех известных вам пакетов и назовёт либо его, либо какое-нибудь следующее за ним в списке.
Читать дальше