Ключевым моментом является то, что memory_order_relaxed
означает, что fetch_add
не заказывает грузы/магазины любым другим местонахождение. Он делает все, что нужно, чтобы быть атомарным и не более. Тем не менее, порядок зависимостей в одном потоке по-прежнему применяется.
Для fetch_add
атомарным, она должна предотвратить любой другой fetch_add
пытается выполнить в то же время от работающих на том же входном значении это чтение. В значительной степени все использует производную от MESI cache-coherence protocol, поэтому на практике атомный fetch_add выполняется путем сохранения строки кэша «заблокирован» в состоянии M от чтения до записи. (Или с LL/SC, обнаружив, что этого не произошло и повторите попытку.) См. Также Can num++ be atomic for 'int num'? для более подробного описания для x86.
C++ ничего не говорит о как, он реализован, но может быть полезно иметь некоторое представление о том, какую аппаратную операцию C++ пытается выявить.
Для x
изначально нулевой, я могу быть уверен, что fire
будет называться по крайней мере один раз в этом случае?
Да, он будет работать хотя бы один раз. Он может работать два раза, так как для обеих нагрузок можно увидеть результат со второго fetch_add
.
Нагрузки всегда видят значение для x
, которое было обновлено, по крайней мере, fetch_add в своем потоке. memory_order_relaxed
не позволяет нить наблюдать за собственными операциями, происходящими не по порядку, и fetch_adds происходят в определенном порядке. Итак, по крайней мере, поток, который «идет вторым», увидит x == 3
.
Непонятно, какой заказ будет. Вы можете наблюдать за ним, глядя на возвращаемое значение fetch_add
вместо использования отдельной нагрузки. (Этот код не соблюдает порядок, установленный в fetch_add
с, потому что несколько потоков могут получить такое же значение. Для этого вам нужно захватить значение в рамках одной атомарной операции.)
Мне также интересно узнать о заказе. Существует явная зависимость между модификацией x и нагрузкой, поэтому я полагаю, что эти инструкции не подлежат переупорядочению, несмотря на то, что указан порядок спокойствия.
Переупорядочение памяти во время выполнения и времени компиляции, а также исполнение вне порядка, сохраняют поведение одного потока. Золотое правило всего этого материала (в том числе правило «как есть» компилятора) - «не разбивать однопоточный код».
Но порядок, в котором эти операции становятся глобально видимыми для других потоков, не гарантируется. Хранилище x.fetch_add
может стать глобально видимым до других нитей после x.load
. Он не будет на x86, поскольку x86 не переупорядочивает магазины с более поздними загрузками с одного и того же адреса (но переупорядочение StoreLoad разрешено для других адресов, even when a store and load partially overlap.)
Третья нить может видеть операции T1 и T2 как происходит в другом порядке, чем T1 видит операции T2. (Т. Е там не должно быть в общей сложности порядка, что все потоки согласования, если вы не используете последовательную согласованность)
Обратите внимание, что «наблюдения» нагрузка становится глобально видимым возможно только косвенно: глядя на другие магазины, сделанные потоком, чтобы выяснить, какое значение должно быть загружено. Но нагрузка - это настоящая и важная часть заказа.
Так что, если fire()
пишет что-то в память, 3-я нить могла видеть, что происходит, пока она еще видит x==0
. Это возможно только в том случае, если он выглядит (в потоке 3), как нагрузка T1, произошедшая до fetch_add в любом потоке. Даже если поток 3 использовал x.fetch_add(mo_relaxed)
для наблюдения за значением в x
, C++ допускает это. Но, как я уже сказал, вы не увидите его на x86.
Обратите внимание, что «порядок зависимостей» - это фраза с другим значением. Даже на слабоупорядоченных процессорах, загружая указатель, а затем разыменовывая его, он гарантирует, что переупорядочение LoadLoad не произойдет. Существует только одна архитектура, которая этого не делала: Alpha требует барьера для этого. Это одна из причин, по которой memory_order_consume
и memory_order_relaxed
являются отдельными.
Вы можете использовать memory_order_consume
вместо memory_order_acquire
для более дешевой синхронизации с производителем mo_release
, если он публикует указатель. (компиляторы обычно просто усиливают потребление для приобретения, потому что их трудно реализовать.)
Значение отношения зависимостей применяется между двумя разными ячейками памяти: указателем и буфером. Это совсем другое дело в том, что поток всегда видит свои собственные действия в порядке.
В потоке всегда будут отображаться данные, по крайней мере такие же новые, как его собственный самый последний магазин, когда есть только один объект. (новый в порядке, который он наблюдает за операциями, а не как новый, как в абсолютном времени! Это просто означает, что поток не может сохранить значение, а затем его следующее чтение обнаруживает, что значение, возвращенное к одному из хранилища, которое уже было видимо перед fetch_add.)
Непросто рассуждать об этом (например, глобальный порядок, наблюдаемый другими потоками, по сравнению с порядком, наблюдаемым одним из задействованных потоков). Перечисление всех удивительных переупорядочений, которые могут быть или могут быть невозможными, также сложно.
Есть некоторые ссылки в lock-free и stdatomic теги wikis. Особенно рекомендую Jeff Preshing's blog. Он хорошо объясняет, и опубликовал много хороших статей.
Вы не знаете сроки разных потоков. Если поток 1 записывается в переменную, а поток 2 читает эту переменную, вы не знаете, читает ли поток 2 _before_ или _after_ записи. Atomic может помочь убедиться, что поток 2 не читает _during_ считывания и получает смесь старого и нового значения; это все. – gnasher729
Какую часть вопроса вы имеете в виду? – mentalmushroom
re: последний абзац: переупорядочение памяти во время выполнения и времени компиляции и исполнение вне порядка, все сохраняют поведение одного потока. Золотое правило всего этого материала (в том числе правило «как есть» компилятора) - «не разбивать однопоточный код». Однако хранимая часть 'x.fetch_add' может стать глобально видимой для * других * потоков после' x.load'. Он не будет на x86, поскольку x86 не переупорядочивает магазины с более поздними загрузками с одного и того же адреса (но переупорядочение StoreLoad разрешено для других адресов). –